import math
from datetime import datetime as dt
from typing import List

import pandas as pd
from py_jftech import component, autowired, filter_weekend, prev_workday

from api import RoboReportor, PortfoliosRisk, PortfoliosHolder, Datum, DatumType, Navs, RoboExecutor


@component(bean_name='contribution-report')
class ContributionReportor(RoboReportor):

    @autowired
    def __init__(self, hold: PortfoliosHolder = None, datum: Datum = None, navs: Navs = None, exec: RoboExecutor = None):
        self._hold = hold
        self._datum = datum
        self._navs = navs
        self._exec = exec

    @property
    def report_name(self) -> str:
        return '贡献率'

    def load_report(self, max_date=dt.today(), min_date=None) -> List[dict]:
        max_date = filter_weekend(max_date)
        min_date = filter_weekend(min_date) if min_date is not None else self._exec.start_date
        result = pd.DataFrame()
        for risk in PortfoliosRisk.values():
            buy_date = None
            sell_date = max_date
            while buy_date is None or sell_date > min_date:
                last_date = sell_date if sell_date == max_date else prev_workday(sell_date)
                buy_date = self._hold.get_last_rebalance_date(risk=risk, max_date=last_date)
                weight = self._hold.get_portfolios_weight(day=last_date, risk=risk)

                datums = pd.DataFrame(self._datum.get_datums(type=DatumType.FUND, datum_ids=tuple(weight.keys())))
                datums = datums[['id', 'ftTicker', 'bloombergTicker', 'chineseName']]
                datums.columns = ['id', 'ft_ticker', 'bloomberg_ticker', 'name']
                datums['ratio'] = datums.apply(lambda row: weight[row.id], axis=1)
                datums['hold'] = (sell_date - buy_date).days

                navs = pd.DataFrame(self._navs.get_fund_navs(fund_ids=tuple(weight.keys()), max_date=sell_date, min_date=buy_date))
                navs = navs.pivot_table(columns='fund_id', index='nav_date', values='nav_cal')
                rtns = navs.iloc[-1] / navs.iloc[0] - 1
                rtns.name = 'rtns'
                datums = datums.join(rtns, on='id')

                datums['risk'] = risk.name
                datums['buy_date'] = buy_date
                datums['sell_date'] = sell_date if sell_date != max_date else math.nan

                datums.drop('id', axis=1, inplace=True)
                result = pd.concat([result, datums], ignore_index=True)
                sell_date = buy_date if buy_date < sell_date else prev_workday(buy_date)
        return result.to_dict('records') if not result.empty else []
