"""Global/Region/Country rankings (spec §34) — a thin wrapper over the same execution path as
screeners (docs/SCREENING.md §5), never a separate code path.

AUDIT FIX (StockLab overhaul, final engineering pass, Part A3, docs/AUDIT_SECURITY_A3.md):
rate-limited at the general default tier -- unauthenticated and DB-hitting on every call (a joined
query across up to 200 securities per request, not a single bounded lookup), but its query shape
is fixed rather than caller-controlled (no arbitrary filter list the way the screener has), so the
general default is judged sufficient rather than the tighter screener/search tier.
"""

from typing import Optional

from fastapi import APIRouter, Depends, Query, Request
from sqlalchemy import func, select
from sqlalchemy.orm import Session

from app.api.v1.deps import limiter
from app.api.v1.serializers import company_summary, is_demo_by_security, security_eager_load_options
from app.core.config import get_settings
from app.core.db import get_db
from app.models import Company, Country, Score, Security, Valuation
from app.schemas.common import ScreenResultRow

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

_RANKINGS_RATE_LIMIT = f"{get_settings().RATE_LIMIT_PER_MINUTE}/minute"

_DIMENSION_SCORE_FIELD = {
    "quality": "quality_score", "value": "valuation_score", "growth": "growth_score",
    "compounder": "overall_score", "fcf": "financial_health_score", "low_debt": "financial_health_score",
    "dividend": "overall_score", "quality_at_reasonable_price": "overall_score",
}


@router.get("/{dimension}", response_model=list[ScreenResultRow])
@limiter.limit(_RANKINGS_RATE_LIMIT)
def get_ranking(
    request: Request, dimension: str, country: Optional[str] = Query(None), sector: Optional[str] = Query(None),
    strong_buy_only: bool = Query(False), limit: int = Query(50, le=200), db: Session = Depends(get_db),
):
    score_field = _DIMENSION_SCORE_FIELD.get(dimension.lower(), "overall_score")
    query = (
        select(Security, Score, Valuation)
        # AUDIT FIX (StockLab overhaul, Part A2, docs/AUDIT_PERFORMANCE.md's remaining
        # company_summary() N+1 finding): same fix as screeners.py -- batches the
        # Security->Company->{country,sector,industry} chain instead of 3 lazy-load queries per row.
        .options(*security_eager_load_options())
        .join(Company, Security.company_id == Company.id)
        .join(
            Score,
            Score.security_id == Security.id,
            Score.calculation_date
            == select(func.max(Score.calculation_date))
            .where(Score.security_id == Security.id)
            .scalar_subquery(),
        )
        .outerjoin(
            Valuation,
            Valuation.security_id == Security.id,
            Valuation.calculation_date
            == select(func.max(Valuation.calculation_date))
            .where(Valuation.security_id == Security.id)
            .scalar_subquery(),
        )
    )
    if country:
        query = query.join(Country, Company.country_id == Country.id).where(Country.iso2 == country.upper())
    if sector:
        query = query.where(Company.sector.has(code=sector.upper()))
    if strong_buy_only:
        query = query.where(Score.recommendation.in_(["STRONG_BUY", "BUY"]))

    query = query.order_by(getattr(Score, score_field).desc().nullslast()).limit(limit)
    rows = db.execute(query).all()

    # AUDIT FIX (StockLab overhaul, Part A2): is_demo is not a mapped relationship (it's derived
    # through FinancialPeriod->Source), so selectinload above can't cover it -- batched separately,
    # same helper/reasoning as screeners.py.
    demo_by_id = is_demo_by_security(db, [sec.id for sec, _, _ in rows])

    return [
        ScreenResultRow(
            company=company_summary(db, sec, is_demo=demo_by_id.get(sec.id, False)),
            overall_score=score.overall_score,
            recommendation=score.recommendation,
            weighted_fair_value=val.weighted_fair_value if val else None,
            margin_of_safety=val.margin_of_safety if val else None,
            # AUDIT FIX (StockLab overhaul, Part A1): score is already joined into this query row,
            # no new query.
            confidence_score=score.confidence_score, data_quality_score=score.data_quality_score,
        )
        for sec, score, val in rows
    ]
