import json
import logging

from py_jftech import component, autowired, format_date
from pymysql import IntegrityError, constants

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

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 = self.build_portfolio(day, type)
                for build_risk, datas in result.items():
                    try:
                        rmp.insert({
                            **datas,
                            'risk': build_risk,
                            'type': type,
                            'date': day
                        })
                    except IntegrityError as e:
                        code, msg = e.args
                        if code != constants.ER.DUP_ENTRY:
                            raise e
                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)
            raise e

    def build_portfolio(self, day, type: PortfoliosType):
        result = {}
        portfolios = {}
        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)
            navs_group = solver.reset_navs(day)
            for category, navs in navs_group.items():
                solver.set_navs(navs)
                solver.set_category(category)
                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)
                portfolios = {**portfolios, **portfolio}
        result[risk] = {
            'solve': SolveType.MPT,
            'portfolio': json.dumps(portfolios),
        } if portfolios else {
            'solve': SolveType.INFEASIBLE
        }
        return result

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

    def get_all_portfolios(self, risk: PortfoliosRisk = None):
        return get_list(risk=risk)


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

    def build_portfolio(self, day, type: PortfoliosType):
        result = {}
        portfolios = {}
        for risk in PortfoliosRisk:
            solver = self._factory.create_solver(risk, type)
            navs_group = solver.reset_navs(day)
            for category, navs in navs_group.items():
                solver.set_navs(navs)
                solver.set_category(category)
                max_rtn, max_var, minCVaR_whenMaxR = solver.solve_max_rtn()
                min_rtn, min_var, maxCVaR_whenMinV = solver.solve_min_rtn()
                mpt_portfolio, mpt_cvar = solver.solve_mpt(min_rtn, max_rtn)
                portfolio, cvar = solver.solve_poem(min_rtn, max_rtn, mpt_cvar, maxCVaR_whenMinV)
                if not portfolio:
                    portfolio = mpt_portfolio
                portfolios = {**portfolios, **portfolio}
            if portfolios:
                result[risk] = {
                    'solve': SolveType.POEM,
                    'portfolio': json.dumps(portfolios),
                }
        return result