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, \
    PortfoliosChecker
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,
                 checker: PortfoliosChecker = None):
        self._assets = assets
        self._navs = navs
        self._datum = datum
        self._factory = factory
        self._checker = checker

    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():
                    datas['portfolio'] = self._checker.check(day, json.loads(datas['portfolio']))
                    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 portfolio 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():
                # count = solver.get_config('asset-count')[0]
                # nav_count = len(navs.columns)
                # if count <= nav_count:
                #     pass
                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)


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

    def build_portfolio(self, day, type: PortfoliosType):
        result = {}
        portfolios = {}
        for risk in PortfoliosRisk:
            solver = self._factory.create_solver(risk, type)
            self.__day = day
            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


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

    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():
                    datas['portfolio'] = self._checker.check(day, json.loads(datas['portfolio']))
                    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.",
                exc_info=e)
            raise e

    def build_portfolio(self, day, type: PortfoliosType):
        result = {}
        detail = {}
        risk = PortfoliosRisk.FT3
        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


@component(bean_name='mpt')
class PoemARCPortfoliosBuilder(MptARCPortfoliosBuilder):

    def build_portfolio(self, day, type: PortfoliosType):
        result, detail = super(PoemARCPortfoliosBuilder, self).build_portfolio(day, type)
        risk = PortfoliosRisk.FT3
        # 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


@component(bean_name='mpt')
class RiskParityARCPortfoliosBuilder(MptPortfoliosBuilder):
    def build_portfolio(self, day, type: PortfoliosType):
        result = {}
        risk = PortfoliosRisk.FT3
        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)
        portfolio = solver.solve_risk_parity()
        result[risk] = {
            'solve': SolveType.RISK_PARITY,
            'portfolio': json.dumps(portfolio),
        } if portfolio else {
            'solve': SolveType.INFEASIBLE
        }
        return result
