"""CFO (Management) endpoints."""

from __future__ import annotations

from datetime import datetime, timedelta, timezone

from fastapi import APIRouter, Depends, HTTPException, Request, status
from slowapi import Limiter
from slowapi.util import get_remote_address
from sqlalchemy import and_, case, func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_current_user
from app.database import get_db
from app.models.portfolio import PortfolioDomain
from app.models.user import User
from app.models.yield_domain import YieldDomain, YieldTransaction
from app.schemas.cfo import (
    CfoKillListRow,
    CfoMonthlyBucket,
    CfoSummaryResponse,
    CfoUpcomingCostRow,
    SetToDropResponse,
)
from app.services.analyze.renewal_cost import get_tld_price_snapshot

router = APIRouter()
limiter = Limiter(key_func=get_remote_address)


def _utcnow() -> datetime:
    return datetime.now(timezone.utc)


def _month_key(dt: datetime) -> str:
    return f"{dt.year:04d}-{dt.month:02d}"


async def _estimate_renewal_cost_usd(db: AsyncSession, domain: str) -> tuple[float | None, str]:
    # If the user stored renewal_cost, we treat it as the source of truth.
    # Else we estimate using our own collected `tld_prices` DB.
    tld = domain.split(".")[-1].lower()
    snap = await get_tld_price_snapshot(db, tld)
    if snap.min_renew_usd is None:
        return None, "unknown"
    return float(snap.min_renew_usd), "tld_prices"


@router.get("/summary", response_model=CfoSummaryResponse)
@limiter.limit("30/minute")
async def cfo_summary(
    request: Request,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """
    CFO dashboard summary:
    - Burn rate timeline (renewal costs)
    - Upcoming costs (30d)
    - Kill list (renewal soon + no yield signals)
    """
    now = _utcnow()
    now_naive = now.replace(tzinfo=None)

    domains = (
        await db.execute(select(PortfolioDomain).where(PortfolioDomain.user_id == current_user.id))
    ).scalars().all()

    # Yield stats (last 60d) by domain
    since_60d = now_naive - timedelta(days=60)
    yd_rows = (
        await db.execute(
            select(
                YieldDomain.domain,
                func.coalesce(func.sum(YieldTransaction.net_amount), 0).label("net_sum"),
                func.coalesce(func.sum(case((YieldTransaction.event_type == "click", 1), else_=0)), 0).label("clicks"),
            )
            .join(
                YieldTransaction,
                and_(YieldTransaction.yield_domain_id == YieldDomain.id, YieldTransaction.created_at >= since_60d),
                isouter=True,
            )
            .where(YieldDomain.user_id == current_user.id)
            .group_by(YieldDomain.domain)
        )
    ).all()
    yield_by_domain = {str(d).lower(): {"net": float(n or 0), "clicks": int(c or 0)} for d, n, c in yd_rows}

    # Monthly buckets next 12 months
    buckets: dict[str, CfoMonthlyBucket] = {}
    for i in range(0, 12):
        d = (now + timedelta(days=30 * i)).replace(day=1)
        buckets[_month_key(d)] = CfoMonthlyBucket(month=_month_key(d), total_cost_usd=0.0, domains=0)

    upcoming_rows: list[CfoUpcomingCostRow] = []
    kill_list: list[CfoKillListRow] = []

    cutoff_30d = now_naive + timedelta(days=30)

    for pd in domains:
        if pd.is_sold:
            continue

        renewal_dt = pd.renewal_date
        if not renewal_dt:
            continue

        if renewal_dt.tzinfo is not None:
            renewal_dt_naive = renewal_dt.astimezone(timezone.utc).replace(tzinfo=None)
        else:
            renewal_dt_naive = renewal_dt

        # cost source: portfolio overrides
        if pd.renewal_cost is not None:
            cost = float(pd.renewal_cost)
            source = "portfolio"
        else:
            cost, source = await _estimate_renewal_cost_usd(db, pd.domain)

        # Monthly burn timeline
        month = _month_key(renewal_dt_naive)
        if month not in buckets:
            buckets[month] = CfoMonthlyBucket(month=month, total_cost_usd=0.0, domains=0)
        if cost is not None:
            buckets[month].total_cost_usd = float(buckets[month].total_cost_usd) + float(cost)
        buckets[month].domains = int(buckets[month].domains) + 1

        # Upcoming 30d
        if now_naive <= renewal_dt_naive <= cutoff_30d:
            upcoming_rows.append(
                CfoUpcomingCostRow(
                    domain_id=pd.id,
                    domain=pd.domain,
                    renewal_date=renewal_dt,
                    renewal_cost_usd=cost,
                    cost_source=source,
                    is_sold=bool(pd.is_sold),
                )
            )

            y = yield_by_domain.get(pd.domain.lower(), {"net": 0.0, "clicks": 0})
            if float(y["net"]) <= 0.0 and int(y["clicks"]) <= 0:
                kill_list.append(
                    CfoKillListRow(
                        domain_id=pd.id,
                        domain=pd.domain,
                        renewal_date=renewal_dt,
                        renewal_cost_usd=cost,
                        cost_source=source,
                        auto_renew=bool(pd.auto_renew),
                        is_dns_verified=bool(getattr(pd, "is_dns_verified", False) or False),
                        yield_net_60d=float(y["net"]),
                        yield_clicks_60d=int(y["clicks"]),
                        reason="No yield signals tracked in the last 60 days and renewal is due within 30 days.",
                    )
                )

    # Sort rows
    upcoming_rows.sort(key=lambda r: (r.renewal_date or now_naive))
    kill_list.sort(key=lambda r: (r.renewal_date or now_naive))

    upcoming_total = sum((r.renewal_cost_usd or 0) for r in upcoming_rows)
    monthly_sorted = [buckets[k] for k in sorted(buckets.keys())]

    return CfoSummaryResponse(
        computed_at=now,
        upcoming_30d_total_usd=float(round(upcoming_total, 2)),
        upcoming_30d_rows=upcoming_rows,
        monthly=monthly_sorted,
        kill_list=kill_list[:50],
    )


@router.post("/domains/{domain_id}/set-to-drop", response_model=SetToDropResponse)
@limiter.limit("30/minute")
async def set_to_drop(
    request: Request,
    domain_id: int,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """
    Mark portfolio domain as 'to drop' by turning off local auto-renew flag.
    (We cannot disable auto-renew at the registrar automatically.)
    """
    pd = (
        await db.execute(
            select(PortfolioDomain).where(and_(PortfolioDomain.id == domain_id, PortfolioDomain.user_id == current_user.id))
        )
    ).scalar_one_or_none()
    if not pd:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Portfolio domain not found")
    pd.auto_renew = False
    pd.updated_at = datetime.utcnow()
    await db.commit()
    return SetToDropResponse(domain_id=pd.id, auto_renew=bool(pd.auto_renew), updated_at=pd.updated_at.replace(tzinfo=timezone.utc))

