import math
from datetime import date, timedelta
from fastapi import APIRouter, Depends, Request, Query
from fastapi.responses import HTMLResponse
from sqlalchemy.orm import Session
from app.database import get_db
from app.models import DailyBar, Contract, PositionRanking

router = APIRouter(prefix="/contracts", tags=["contracts"])

WEEKDAY_ZH = {0: "周一", 1: "周二", 2: "周三", 3: "周四", 4: "周五", 5: "周六", 6: "周日"}
PAGE_SIZE = 7


def get_active_contracts(db: Session) -> list[str]:
    contracts = (
        db.query(Contract.code)
        .filter(Contract.is_active == True)
        .order_by(Contract.code)
        .all()
    )
    return [c[0] for c in contracts]


@router.get("/", response_class=HTMLResponse)
def contract_index(request: Request, db: Session = Depends(get_db)):
    from app.models import Product

    # Build product → contracts mapping, ordered by product code
    products = (
        db.query(Product)
        .order_by(Product.code)
        .all()
    )

    product_groups = []
    all_codes = []
    for prod in products:
        codes = [
            c.code for c in prod.contracts if c.is_active
        ]
        if codes:
            product_groups.append({
                "code": prod.code,
                "name": prod.name,
                "exchange": prod.exchange,
                "contracts": codes,
            })
            all_codes.extend(codes)

    latest = (
        db.query(DailyBar)
        .filter(DailyBar.contract.in_(all_codes))
        .order_by(DailyBar.date.desc())
        .all()
    )
    contract_bars = {}
    for bar in latest:
        if bar.contract not in contract_bars:
            contract_bars[bar.contract] = bar

    # Next-day amplitude: mean of latest 5 diffs per contract
    contract_next_amp = {}
    for code in all_codes:
        diffs = [
            r[0] for r in
            db.query(DailyBar.high - DailyBar.low)
            .filter(DailyBar.contract == code)
            .order_by(DailyBar.date.desc())
            .limit(5)
            .all()
        ]
        if len(diffs) >= 5:
            contract_next_amp[code] = round(sum(diffs) / 5)

    template = request.app.state.templates.get_template("index.html")
    return HTMLResponse(
        template.render(
            request=request,
            active_nav="contracts",
            product_groups=product_groups,
            contract_bars=contract_bars,
            contract_next_amp=contract_next_amp,
        )
    )


@router.get("/{contract}", response_class=HTMLResponse)
def contract_detail(
    request: Request,
    contract: str,
    pos_date: str | None = None,
    page: int = Query(1, ge=1),
    db: Session = Depends(get_db),
):
    active_contracts = get_active_contracts(db)
    bars = (
        db.query(DailyBar)
        .filter(DailyBar.contract == contract.upper())
        .order_by(DailyBar.date.desc())
        .all()
    )

    total = len(bars)
    total_pages = max(1, math.ceil(total / PAGE_SIZE))
    page = min(page, total_pages)
    start = (page - 1) * PAGE_SIZE
    page_bars = bars[start:start + PAGE_SIZE]

    rows = []
    for i, bar in enumerate(page_bars):
        global_idx = start + i
        rows.append({
            "global_idx": global_idx,
            "date": bar.date.strftime("%Y/%-m/%-d"),
            "weekday": WEEKDAY_ZH.get(bar.date.weekday(), ""),
            "open": int(bar.open) if bar.open else "-",
            "close": int(bar.close) if bar.close else "-",
            "high": int(bar.high) if bar.high else "-",
            "low": int(bar.low) if bar.low else "-",
            "diff": int(bar.diff) if bar.diff else 0,
            "amp_5d": int(bar.amp_5d) if bar.amp_5d is not None else None,
            "has_amp": bar.amp_5d is not None,
        })

    latest = bars[0] if bars else None

    # Dates that have position data (for date picker)
    pos_dates = [
        r[0] for r in
        db.query(PositionRanking.date)
        .filter(PositionRanking.contract_code == contract.upper())
        .distinct()
        .order_by(PositionRanking.date.desc())
        .all()
    ]

    # Determine which date to show position rankings for
    if pos_date:
        try:
            selected_date = date.fromisoformat(pos_date)
        except ValueError:
            selected_date = pos_dates[0] if pos_dates else None
    else:
        selected_date = pos_dates[0] if pos_dates else None

    # Position rankings for the selected date
    pos_data = {"volume": [], "long": [], "short": []}
    if selected_date:
        rankings = (
            db.query(PositionRanking)
            .filter(
                PositionRanking.contract_code == contract.upper(),
                PositionRanking.date == selected_date,
            )
            .order_by(PositionRanking.data_type, PositionRanking.rank)
            .all()
        )
        for r in rankings:
            pos_data[r.data_type].append({
                "rank": r.rank,
                "institution": r.institution,
                "value": r.value,
                "change": r.change,
            })

    # Predict next trading day amplitude: mean of latest 5 diffs
    # Compute next trading date
    next_date = latest.date + timedelta(days=1) if latest else None
    if next_date and next_date.weekday() >= 5:
        next_date += timedelta(days=7 - next_date.weekday())

    # Predict next trading day amplitude
    next_amp = None
    if len(bars) >= 5:
        next_amp = round(sum(b.diff for b in bars[:5]) / 5)
    template = request.app.state.templates.get_template("contract.html")
    return HTMLResponse(
        template.render(
            request=request,
            active_nav="contracts",
            contract=contract.upper(),
            contracts=active_contracts,
            rows=rows,
            latest=latest,
            pos_dates=pos_dates,
            selected_pos_date=selected_date,
            pos_data=pos_data,
            next_amp=next_amp,
            next_date=next_date.strftime("%Y/%-m/%-d") if next_date else None,
            next_weekday=WEEKDAY_ZH.get(next_date.weekday(), "") if next_date else "",
            row_count=len(rows),
            total_rows=total,
            page=page,
            total_pages=total_pages,
            pos_date=pos_date or "",
            WEEKDAY_ZH=WEEKDAY_ZH,
        )
    )
