import datetime as dt
import json
import logging
import os
from tempfile import TemporaryDirectory

import pandas as pd
import uvicorn
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.date import DateTrigger
from dateutil.relativedelta import relativedelta
from empyrical import annual_volatility, annual_return
from fastapi import FastAPI, Request
from py_jftech import prev_workday, filter_weekend, autowired, sendmail, format_date, get_config
from starlette.responses import JSONResponse

import main
from api import DatumType, PortfoliosRisk, Datum, RoboReportor

app = FastAPI()
# 创建 AsyncIOScheduler 实例
scheduler = AsyncIOScheduler()

REC_GID = get_config('web.guid')
fund_infos, cp, roi, risk = None, None, None, None


async def send_email():
    with TemporaryDirectory() as tmpdir:
        filepath = os.path.join(tmpdir, "portfolio.json")
        with open(filepath, "w", encoding='utf-8') as file:
            recommends = await recommend()
            json.dump(recommends, file, indent=4, ensure_ascii=False)
        email = get_config('reports.exports.real-daily.email')
        receives = email['receives']
        copies = email['copies'] if 'copies' in email and email['copies'] is not None else []
        subject = email['subject']['rebalance'].format(today=format_date(dt.date.today()))
        content = email['content']['rebalance']
        sendmail(receives=receives, copies=copies, subject=subject, content=content, attach_paths=[filepath])


async def save_json():
    if get_config('web.save-path'):
        recommends = await recommend()
        filepath = os.path.join(os.path.abspath(get_config('web.save-path')), f'{REC_GID}.json')
        with open(filepath, "w", encoding='utf-8') as file:
            json.dump(recommends, file, indent=4, ensure_ascii=False)


def get_today_rec():
    from portfolios.dao import robo_mpt_portfolios as rmp
    from api import PortfoliosType, PortfoliosRisk
    day = prev_workday(filter_weekend(dt.date.today()))
    portfolio = rmp.get_one(day, PortfoliosType.NORMAL, PortfoliosRisk.FT3)
    return portfolio


def get_last_signal():
    from rebalance.dao import robo_rebalance_signal as rrs

    day = prev_workday(filter_weekend(dt.date.today()))
    last_re = rrs.get_last_one(max_date=day, risk=PortfoliosRisk.FT3, effective=True)
    return last_re


@autowired
def get_fund_infos(datum: Datum = None):
    global fund_infos
    fund_infos = datum.get_datums(DatumType.FUND)


@autowired(names={'combo': 'hold-report'})
def load_report(max_date=None, min_date=None, combo: RoboReportor = None):
    global cp, roi, risk
    datas = pd.DataFrame(combo.load_report(max_date=max_date, min_date=min_date))
    datas.set_index('date', inplace=True)
    datas = datas['fund_av']
    returns = round(datas.pct_change(), 5)
    roi = round(annual_return(returns) * 100, 2)
    risk = round(annual_volatility(returns) * 100, 2)
    cp = round(roi / risk, 2)
    return cp, roi, risk


def build_overview(asset_weights: dict):
    overview = {
        "name": get_config('web.name'),
        "feature": get_config('web.feature'),
        "tag": get_config('web.tag'),
        "month": f"{dt.date.today().month}月",
        "setting": [{"name": k, "rate": str(int(v))} for k, v in asset_weights.items()]
    }
    return overview


@app.get("/franklin/recommend")
async def recommend():
    sig = get_today_rec()
    if sig:
        if not fund_infos:
            get_fund_infos()
        id_ticker_map = {str(info['id']): info for info in fund_infos}
        funds = json.loads(sig['portfolio'])
        rec_list = []
        portfolios = {'recomm_guid': REC_GID}
        # 二月更新数据
        if dt.date.today().month < 2:
            last_year = dt.datetime(dt.date.today().year - 2, 12, 31)
        else:
            last_year = dt.datetime(dt.date.today().year - 1, 12, 31)
        load_report(max_date=last_year, min_date=last_year - relativedelta(years=10))
        data = {'recomm_guid': REC_GID, 'data_date': sig['create_time'].strftime('%Y-%m-%d'),
                'funds': [{'weight': round(weight * 100), 'fund_id': id_ticker_map[key]['ftTicker']} for key, weight in
                          funds.items()], 'creat_date': sig['create_time'].strftime('%Y-%m-%d %H:%M:%S'),
                'risk': risk,
                'rr': round(sum([id_ticker_map[key]['risk'] * weight for key, weight in funds.items()]), 2), 'cp': cp,
                'roi': roi}
        note = {'recomm_reason': "推荐的理由是:"}
        data['note'] = json.dumps(note, ensure_ascii=False)
        # 计算股债比
        stock_weight = int(
            sum(weight * 100 for key, weight in funds.items() if id_ticker_map[key]['assetType'] == 'STOCK'))
        data["p_note"] = f"{stock_weight}:{100 - stock_weight}"
        portfolios['data'] = data
        # funds根据assetType分类，计算比重
        asset_weights = {}
        for key, weight in funds.items():
            asset_type = id_ticker_map[key]['assetType']
            if asset_type not in asset_weights:
                asset_weights[asset_type] = 0
            asset_weights[asset_type] += weight * 100
        overview = build_overview(asset_weights)
        portfolios['overview'] = overview
        rec_list.append(portfolios)
        return rec_list
    else:
        return {'msg': '当日投组未产生，待10:30后获取'}


@app.get("/franklin/recommend/save")
async def recommend_save():
    await save_json()
    return {'msg': '保存成功'}


# 其他异常处理程序
@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):
    # 打印一般错误信息
    logging.error(f"请求 {request.url} 发生未知错误: {str(exc)}")
    return JSONResponse(
        status_code=500,
        content={"errorCode": "500", "errorMsg": str(exc)},
    )


# 定义应用启动事件
@app.on_event("startup")
async def startup_event():
    # 异常情况可以重启跑当天投组
    current_time = dt.datetime.now()
    target_time = dt.time(10, 20)
    if current_time.time() > target_time:
        scheduler.add_job(main.start, trigger=DateTrigger(run_date=current_time))
        # await send_email()
    # 开启定时任务，执行实盘
    scheduler.add_job(main.start, 'cron', day=1, hour=10, minute=25)
    scheduler.add_job(send_email, 'cron', day=1, hour=10, minute=25)
    scheduler.start()


if __name__ == "__main__":
    uvicorn.run("robo_controller:app", host="0.0.0.0", port=get_config('web.port'))
