"""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, and_
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,
            and_(
                Score.security_id == Security.id,
                Score.id
                == select(Score.id)
                .distinct(Score.security_id)
                .order_by(
                    Score.security_id,
                    Score.calculation_date.desc(),
                )
                .limit(1)
                .correlate(Security)
                .scalar_subquery(),
            ),
        )
        .outerjoin(
            Valuation,
            and_(
                Valuation.security_id == Security.id,
                Valuation.id
                == select(Valuation.id)
                .distinct(Valuation.security_id)
                .order_by(
                    Valuation.security_id,
                    Valuation.calculation_date.desc(),
                )
                .limit(1)
                .correlate(Security)
                .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
    ]
