"""Screener endpoints (spec §35, docs/SCREENING.md).

AUDIT FIX (StockLab overhaul, final engineering pass, Part A3, docs/AUDIT_SECURITY_A3.md):
POST /run rate-limited -- unauthenticated, and cost scales with the number of filters a caller
supplies (each compiles to its own correlated EXISTS subquery). /strategies and /strategies/{key}
are left undecorated: both are fixed, cheap, in-memory dict lookups (BUILT_IN_STRATEGIES,
app/engines/screening/presets.py) with no DB query and no caller-controlled cost -- rate limiting
them would add friction with no real risk being mitigated, exactly the "don't mechanically
decorate every endpoint" case this pass's instructions warn against.
"""

from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.orm import Session

from app.api.v1.deps import limiter
from app.api.v1.serializers import (
    company_summary, is_demo_by_security, latest_scores_by_security, latest_valuations_by_security,
    security_eager_load_options,
)
from app.core.config import get_settings
from app.core.db import get_db
from app.engines.screening.executor import InvalidScreenFilter, build_screen_query
from app.engines.screening.presets import BUILT_IN_STRATEGIES
from app.schemas.common import ScreenRequest, ScreenResponse, ScreenResultRow

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

_SCREENER_RATE_LIMIT = f"{get_settings().SCREENER_RATE_LIMIT_PER_MINUTE}/minute"


@router.get("/strategies")
def list_strategies():
    return [{"key": k, "name": v["name"]} for k, v in BUILT_IN_STRATEGIES.items()]


@router.get("/strategies/{key}")
def get_strategy(key: str):
    strategy = BUILT_IN_STRATEGIES.get(key.upper())
    if strategy is None:
        raise HTTPException(404, f"Unknown strategy: {key}")
    return strategy


@router.post("/run", response_model=ScreenResponse)
@limiter.limit(_SCREENER_RATE_LIMIT)
def run_screen(request: Request, payload: ScreenRequest, db: Session = Depends(get_db)):
    # NOTE: the `request: Request` parameter above is required by slowapi (it locates the
    # per-request state the Limiter needs) and shadows what used to be this function's own
    # ScreenRequest parameter -- that's renamed to `payload` throughout this function, matching the
    # same rename auth.py already used for its Pydantic body params for the same reason.
    try:
        # AUDIT FIX (StockLab overhaul, Part A2, docs/AUDIT_PERFORMANCE.md's remaining
        # company_summary() N+1 finding): security_eager_load_options() batches the
        # Security->Company->{country,sector,industry} chain into a fixed number of extra queries
        # instead of 3 per row.
        query = build_screen_query(payload).options(*security_eager_load_options())
    except InvalidScreenFilter as e:
        raise HTTPException(400, str(e))

    securities = db.execute(query).scalars().all()

    # AUDIT FIX (StockLab overhaul, performance audit, docs/AUDIT_PERFORMANCE.md finding #2): this
    # loop used to run a separate Score query AND a separate Valuation query per security (2N
    # queries for N results, on top of company_summary's own per-row queries below) -- a classic
    # N+1. Both lookups are now batched into one query each via a single IN(...) over all
    # security_ids in this page of results, before the loop. is_demo_by_security below is the same
    # fix applied to company_summary()'s remaining is_demo lazy load (Part A2).
    security_ids = [sec.id for sec in securities]
    scores_by_id = latest_scores_by_security(db, security_ids)
    valuations_by_id = latest_valuations_by_security(db, security_ids)
    demo_by_id = is_demo_by_security(db, security_ids)

    rows = []
    for sec in securities:
        score = scores_by_id.get(sec.id)
        val = valuations_by_id.get(sec.id)
        rows.append(ScreenResultRow(
            company=company_summary(db, sec, is_demo=demo_by_id.get(sec.id, False)),
            overall_score=score.overall_score if score else None,
            recommendation=score.recommendation if score else None,
            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 in hand from the batched
            # lookup above, no new query.
            confidence_score=score.confidence_score if score else None,
            data_quality_score=score.data_quality_score if score else None,
        ))
    return ScreenResponse(total=len(rows), results=rows)
