from datetime import datetime as dt
from typing import List

import pandas as pd
from py_jftech import get_config, component, autowired, to_tuple

from api import Navs, Datum, DatumType, RoboReportor
from basic.dao import robo_exrate as re, robo_fund_navs as rfn, robo_index_datas as rid, robo_eco_datas as red


@component
class DefaultNavs(Navs):

    @autowired
    def __init__(self, datum: Datum = None):
        self._config = get_config(__name__)
        self._datum = datum

    def get_fund_navs(self, fund_ids=None, min_date=None, max_date=None):
        navs = rfn.get_navs(fund_id=fund_ids, min_date=min_date, max_date=max_date)
        if navs and 'exrate' in self._config:
            navs = pd.DataFrame(navs)
            navs = navs.pivot_table(index='nav_date', columns='fund_id', values='nav_cal')
            for exrate_config in self._config['exrate']:
                exrate = pd.DataFrame(re.get_exrates(ticker=exrate_config['ticker'], min_date=navs.index.min(),
                                                     max_date=navs.index.max()))
                exrate = exrate[['date', 'close']]
                exrate.set_index('date', inplace=True)
                for fund in self._datum.get_datums(type=DatumType.FUND, crncy=exrate_config['from']):
                    if fund['id'] in navs.columns:
                        navs[fund['id']] = round(navs[fund['id']] * exrate['close'], 4)
            navs = navs.reset_index().melt(id_vars='nav_date', value_name='nav_cal')
            navs.dropna(inplace=True)
            navs = navs[['fund_id', 'nav_date', 'nav_cal']]
            navs.sort_values(by=['fund_id', 'nav_date'], inplace=True)
            navs = navs.to_dict('records')
        return navs

    def get_nav_start_date(self, fund_ids=None):
        return {x['fund_id']: x['min_date'] for x in rfn.get_min_dates(fund_ids=fund_ids)}

    def get_index_close(self, datum_ids=None, min_date=None, max_date=None, ticker=None):
        datum_ids = to_tuple(datum_ids)
        if ticker:
            datums = self._datum.get_datums(type=DatumType.INDEX, ticker=ticker)
            datum_ids = tuple(set(datum_ids or []) | {x['id'] for x in datums})
        results = rid.get_list(index_ids=datum_ids, min_date=min_date, max_date=max_date)
        return [{'index_id': x['index_id'], 'date': x['date'], 'close': x['close']} for x in results]

    def get_last_index_close(self, max_date, datum_id=None, ticker=None, count=1):
        if not datum_id:
            assert ticker, "get last index close, datum_id and ticker give at least one"
            datum = self._datum.get_datums(type=DatumType.INDEX, ticker=ticker)
            datum_id = datum[0]['id'] if datum else None
        assert datum_id, "get last index close, datum id is not found"
        assert max_date, "get last index close, start_date is not found"
        if count == 1:
            last = rid.get_last_one(index_id=datum_id, max_date=max_date)
            return {
                'index_id': last['index_id'],
                'date': last['date'],
                'close': last['close']
            } if last else None
        else:
            last = rid.get_last(index_id=datum_id, max_date=max_date, count=count)
            return [{
                'index_id': x['index_id'],
                'date': x['date'],
                'close': x['close']
            } for x in last] if last else None

    def get_eco_values(self, datum_ids=None, min_date=None, max_date=None, ticker=None, by_release_date=False):
        datum_ids = to_tuple(datum_ids)
        if ticker:
            datums = self._datum.get_datums(type=DatumType.INDEX, ticker=ticker)
            datum_ids = tuple(set(list(datum_ids or []) | {x['id'] for x in datums}))
        return red.get_list(eco_ids=datum_ids, min_date=min_date, max_date=max_date, by_release_date=by_release_date)

    def get_last_eco_values(self, max_date, datum_id=None, ticker=None, count=1, by_release_date=False):
        if not datum_id:
            assert ticker, "get last eco close, datum_id and ticker give at least one"
            datum = self._datum.get_datums(type=DatumType.ECO, ticker=ticker)
            datum_id = datum[0]['id'] if datum else None
        assert datum_id, "get last eco close, datum id is not found"
        assert max_date, "get last eco close, start_date is not found"
        if count == 1:
            return red.get_last_one(eco_id=datum_id, max_date=max_date, by_release_date=by_release_date)
        else:
            return red.get_last(eco_id=datum_id, max_date=max_date, count=count, by_release_date=by_release_date)


@component(bean_name='navs-report')
class NavsReportor(RoboReportor):

    @autowired
    def __init__(self, datum: Datum = None, navs: Navs = None):
        self._datum = datum;
        self._navs = navs
        self._config = get_config('reports.navs')

    @property
    def report_name(self) -> str:
        return "基金净值"

    @property
    def tickers(self):
        return self._config['tickers'] if 'tickers' in self._config else None

    @property
    def type(self):
        return DatumType[self._config['type']]

    def load_report(self, max_date=dt.today(), min_date=None) -> List[dict]:
        asset_ids = {x['id']: x for x in self._datum.get_datums(ticker=self.tickers, type=self.type)}
        if self.type == DatumType.FUND:
            result = pd.DataFrame(self._navs.get_fund_navs(fund_ids=tuple(asset_ids.keys()), max_date=max_date, min_date=min_date))
            result = result.pivot_table(index='nav_date', columns='fund_id', values='nav_cal')
            result.rename(columns={x[0]: x[1]['bloombergTicker'] for x in asset_ids.items()}, inplace=True)
            result.reset_index(inplace=True)
            return result.to_dict('records')
        return []