import json
import logging

from py_jftech import component, autowired, format_date

from api import PortfoliosBuilder, PortfoliosRisk, AssetPool, Navs, PortfoliosType, Datum, SolveType, SolverFactory
from portfolios.dao import robo_mpt_portfolios as rmp

logger = logging.getLogger(__name__)


@component(bean_name='mpt')
class MptPortfoliosBuilder(PortfoliosBuilder):

    @autowired
    def __init__(self, assets: AssetPool = None, navs: Navs = None, datum: Datum = None, factory: SolverFactory = None):
        self._assets = assets
        self._navs = navs
        self._datum = datum
        self._factory = factory

    def get_portfolios(self, day, risk: PortfoliosRisk, type: PortfoliosType = PortfoliosType.NORMAL):
        try:
            portfolio = rmp.get_one(day, type, risk)
            if not portfolio:
                result, detail = self.build_portfolio(day, type)
                for build_risk, datas in result.items():
                    rmp.insert({
                        **datas,
                        'risk': build_risk,
                        'type': type,
                        'date': day
                    })
                portfolio = rmp.get_one(day, type, risk)
            if SolveType(portfolio['solve']) is not SolveType.INFEASIBLE:
                result = json.loads(portfolio['portfolio'])
                return {int(x[0]): x[1] for x in result.items()}
            return None
        except Exception as e:
            logger.exception(f"build protfolio of type[{type.name}] and risk[{risk.name}] with date[{format_date(day)}] failure.", e)

    def build_portfolio(self, day, type: PortfoliosType):
        result = {}
        detail = {}
        for risk in PortfoliosRisk:
            logger.info(
                f"start to build protfolio of type[{type.name}] and risk[{risk.name}] with date[{format_date(day)}]")
            solver = self._factory.create_solver(risk, type)
            solver.reset_navs(day)
            logger.debug({
                'Khist': len(solver.rtn_history),
                'beta': solver.get_config('mpt.cvar-beta'),
                'Kbeta': solver.k_beta,
            })
            max_rtn, max_var, minCVaR_whenMaxR = solver.solve_max_rtn()
            min_rtn, min_var, maxCVaR_whenMinV = solver.solve_min_rtn()
            portfolio, cvar = solver.solve_mpt(min_rtn, max_rtn)
            result[risk] = {
                'solve': SolveType.MPT,
                'portfolio': json.dumps(portfolio),
                'cvar': cvar
            } if portfolio else {
                'solve': SolveType.INFEASIBLE
            }
            detail[risk] = {
                'max_rtn': max_rtn,
                'max_var': max_var,
                'minCVaR_whenMaxR': minCVaR_whenMaxR,
                'min_rtn': min_rtn,
                'min_var': min_var,
                'maxCVaR_whenMinV': maxCVaR_whenMinV,
            }
        return result, detail

    def clear(self, day=None, risk: PortfoliosRisk = None):
        rmp.delete(min_date=day, risk=risk)


@component(bean_name='poem')
class PoemPortfoliosBuilder(MptPortfoliosBuilder):

    def build_portfolio(self, day, type: PortfoliosType):
        result, detail = super(PoemPortfoliosBuilder, self).build_portfolio(day, type)
        for risk in PortfoliosRisk:
            if result[risk]['solve'] is SolveType.INFEASIBLE:
                continue
            solver = self._factory.create_solver(risk, type)
            solver.reset_navs(day)
            min_rtn = detail[risk]['min_rtn']
            max_rtn = detail[risk]['max_rtn']
            mpt_cvar = result[risk]['cvar']
            maxCVaR_whenMinV = detail[risk]['maxCVaR_whenMinV']
            portfolio, cvar = solver.solve_poem(min_rtn, max_rtn, mpt_cvar, maxCVaR_whenMinV)
            if portfolio:
                result[risk] = {
                    'solve': SolveType.POEM,
                    'portfolio': json.dumps(portfolio),
                    'cvar': cvar
                }
                detail[risk]['mpt_cvar'] = mpt_cvar
        return result, detail