"""/api/promo/* router.

Thin layer: parse query params, call PromoService, return. Endpoints:
    GET  /api/promo/history          — contiguous-run promo events
    GET  /api/promo/performance      — same events with uplift math
    GET  /api/promo/proposals        — list planner submissions
    POST /api/promo/proposals        — create a new proposal
    GET  /api/promo/analog-forecast  — analog-based uplift forecast for a SKU
"""
from __future__ import annotations

from typing import Optional

from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session

from backend.models.database import get_db
from backend.schemas.promo import (
    AcknowledgeConflictAction,
    AnalogForecast,
    ApprovalAction,
    ApprovalActionResponse,
    ApprovalRow,
    CalendarConflictsResponse,
    CalendarResponse,
    CalendarStatsResponse,
    ForecasterResponse,
    MarketingCampaignDetail,
    MarketingHistoryResponse,
    Nc30BatchItem,
    Nc30CheckResult,
    ParentGroupsResponse,
    PromoHistoryResponse,
    PromoPerformanceResponse,
    PromoProposal,
    PromoProposalCreate,
    PromoProposalListResponse,
    PromoProposalUpdate,
    PromoSkuDetail,
    PromoSkuOverlap,
    SkuSnapshot,
    StatusChangeAction,
    StatusChangeResponse,
)
from backend.services.promo_service import PromoService

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


@router.get("/health")
def health() -> dict:
    return {"status": "ok"}


@router.get("/history", response_model=PromoHistoryResponse)
def get_history(
    category: list[str] = Query(default=[]),
    tier:     list[str] = Query(default=[]),
    min_yw:   Optional[int] = Query(None, description="Min year_week (e.g. 202601)"),
    max_yw:   Optional[int] = Query(None, description="Max year_week"),
    max_events: int = Query(5000, ge=10, le=20000),
    db: Session = Depends(get_db),
) -> PromoHistoryResponse:
    """Historical promo events — one row per contiguous run of
    erp_promo_weeks per SKU. ordered most-recent first."""
    return PromoService(db).get_promo_history(
        category=category or None,
        tier=tier or None,
        min_start_yw=min_yw,
        max_start_yw=max_yw,
        max_events=max_events,
    )


@router.get("/performance", response_model=PromoPerformanceResponse)
def get_performance(
    category: list[str] = Query(default=[]),
    tier:     list[str] = Query(default=[]),
    max_weeks: int = Query(13, ge=1, le=52, description="Cap on event duration"),
    min_yw:   Optional[int] = Query(None),
    max_events: int = Query(5000, ge=10, le=20000),
    db: Session = Depends(get_db),
) -> PromoPerformanceResponse:
    """Same events as /history, plus before/during/after qty and the
    derived uplift / cannibalization / net_effect. Math mirrors
    `build_promo_performance.py`. Filters out evergreen flags via
    max_weeks (default 13)."""
    return PromoService(db).get_promo_performance(
        category=category or None,
        tier=tier or None,
        max_weeks=max_weeks,
        min_start_yw=min_yw,
        max_events=max_events,
    )


@router.get("/proposals", response_model=PromoProposalListResponse)
def list_proposals(
    status: Optional[str] = Query(None, pattern="^(draft|submitted|approved|rejected|revision)$"),
    proposed_by_id: Optional[int] = Query(None),
    db: Session = Depends(get_db),
) -> PromoProposalListResponse:
    """List promo_proposals. Filter by status or submitter. Ordered
    newest-first."""
    return PromoService(db).get_proposals(
        status=status,
        proposed_by_id=proposed_by_id,
    )


@router.post("/proposals", response_model=PromoProposal)
def create_proposal(
    payload: PromoProposalCreate,
    db: Session = Depends(get_db),
) -> PromoProposal:
    """Submit a new promo proposal. Returns the freshly-created row
    (id + created_at populated by the DB) shaped exactly like the list
    endpoint so the frontend can append without an extra fetch."""
    created = PromoService(db).submit_proposal(payload.model_dump())
    # Refetch via the list flow to get the full shape (user join, cw_label)
    result = PromoService(db).get_proposals()
    for p in result["proposals"]:
        if p["id"] == created.get("id"):
            return p   # type: ignore[return-value]
    # Fallback: return a minimal stub if the refetch missed (shouldn't happen)
    return PromoProposal(
        id=int(created.get("id") or 0),
        status=payload.status,
        n_skus=len(payload.skus),
        skus=payload.skus,
    )


@router.put("/proposals/{proposal_id}", response_model=PromoProposal)
def update_proposal(
    proposal_id: int,
    payload: PromoProposalUpdate,
    db: Session = Depends(get_db),
) -> PromoProposal:
    """Overwrite an existing proposal's content — used when a planner reopens
    a saved draft (or a sent-back revision) in the Planner and re-saves it.
    Returns the freshly-shaped row. Use POST /proposals to create a new one."""
    data = payload.model_dump(exclude_unset=True)
    if "skus" in data and data["skus"] is not None:
        # pydantic gave us SkuLine models when set; model_dump already
        # converted nested models to dicts via exclude_unset.
        data["skus"] = [dict(s) for s in data["skus"]]
    try:
        return PromoService(db).update_proposal(proposal_id=proposal_id, data=data)  # type: ignore[return-value]
    except ValueError as e:
        from fastapi import HTTPException
        raise HTTPException(status_code=404, detail=str(e))


@router.get("/analog-forecast", response_model=AnalogForecast)
def get_analog_forecast(
    sku: str = Query(..., description="SKU code, e.g. POL09735"),
    mechanic: Optional[str] = Query(None, description="Discount % / 1+1 / 2+1 / 3+1"),
    discount: Optional[float] = Query(None, ge=0, le=90, description="Discount percentage"),
    group_skus: list[str] = Query(default=[], description="Parent-group sibling SKUs — used to seed a baseline for a NEW SKU with no own sales"),
    db: Session = Depends(get_db),
) -> AnalogForecast:
    """Analog-based uplift forecast for one SKU. Walks the SKU's own
    historical promos, weights them by similarity to the proposed
    mechanic/discount, applies the calibrated discount-band curve and
    price-disruptor multiplier. Returns expected uplift + p90 upside.

    For a NEW SKU with no own baseline, pass its parent-group siblings in
    group_skus to seed the baseline from the group average."""
    return PromoService(db).get_analog_forecast(
        sku=sku,
        mechanic=mechanic,
        discount_pct=discount,
        group_skus=group_skus or None,
    )


# ----------------------------------------------------------------------
# NC30 compliance
# ----------------------------------------------------------------------

@router.get("/nc30", response_model=Nc30CheckResult)
def get_nc30(
    sku: str = Query(..., description="SKU code"),
    promo_price: float = Query(..., ge=0, description="Planned promo price (€)"),
    db: Session = Depends(get_db),
) -> Nc30CheckResult:
    """Check whether a planned promo price clears the Croatian
    'lowest price in the past 30 days' rule (NC30). Returns ok=true
    when within tolerance OR when NC30 reference is unavailable
    (has_data=false)."""
    return PromoService(db).check_nc30(
        sku=sku, planned_promo_price=promo_price,
    )


@router.post("/nc30/batch", response_model=list[Nc30CheckResult])
def post_nc30_batch(
    items: list[Nc30BatchItem],
    db: Session = Depends(get_db),
) -> list[Nc30CheckResult]:
    """Bulk NC30 check — used by the planner when many SKUs are selected
    and the user changes the discount %."""
    return PromoService(db).check_nc30_batch(items=[i.model_dump() for i in items])


# ----------------------------------------------------------------------
# SKU snapshot for the planner
# ----------------------------------------------------------------------

@router.get("/sku-snapshot", response_model=SkuSnapshot)
def get_sku_snapshot(
    sku: str = Query(..., description="SKU code"),
    db: Session = Depends(get_db),
) -> SkuSnapshot:
    """One-shot snapshot of price, cost, NC30, baseline, on-hand etc.
    The Planner calls this once per selected SKU to populate its tab."""
    snap = PromoService(db).get_sku_snapshot(sku=sku)
    if snap is None:
        from fastapi import HTTPException
        raise HTTPException(status_code=404, detail=f"SKU '{sku}' not found")
    return snap   # type: ignore[return-value]


# ----------------------------------------------------------------------
# Parent (product-family) groups
# ----------------------------------------------------------------------

@router.get("/parent-groups", response_model=ParentGroupsResponse)
def get_parent_groups(
    min_size: int = Query(2, ge=2, le=50, description="Minimum group size"),
    db: Session = Depends(get_db),
) -> ParentGroupsResponse:
    """Returns product families — groups of variants that share a name
    stem (e.g. '100R Whey 2kg' has 4 flavor variants). The Planner uses
    this to add all variants in one click."""
    return PromoService(db).get_parent_groups(min_size=min_size)


# ----------------------------------------------------------------------
# Forecaster — SKU × outcome → recommended discount
# ----------------------------------------------------------------------

@router.get("/forecaster", response_model=ForecasterResponse)
def get_forecaster(
    sku: str = Query(..., description="SKU code"),
    outcome: str = Query("traffic", description="stock_clear / margin / acquisition / traffic"),
    db: Session = Depends(get_db),
) -> ForecasterResponse:
    """Outcome-anchored discount recommendation. Picks a depth, applies
    breakeven compliance auto-lowering, returns predicted uplift +
    full P&L + discount sensitivity table + history backing."""
    return PromoService(db).get_forecaster(sku=sku, outcome=outcome)


# ----------------------------------------------------------------------
# Marketing history (Magento coupon log)
# ----------------------------------------------------------------------

@router.get("/marketing-history", response_model=MarketingHistoryResponse)
def get_marketing_history(db: Session = Depends(get_db)) -> MarketingHistoryResponse:
    """List of webshop coupon campaigns with aggregate metrics. Empty
    response with a note when no Magento data has been imported."""
    return PromoService(db).get_marketing_history()


@router.get("/marketing-history/{campaign_id}", response_model=MarketingCampaignDetail)
def get_marketing_campaign_detail(
    campaign_id: int,
    db: Session = Depends(get_db),
) -> MarketingCampaignDetail:
    """Per-campaign drill-down: SKUs, coupon codes, daily timeline."""
    return PromoService(db).get_marketing_campaign_detail(campaign_id=campaign_id)


# ======================================================================
# Calendar — proposals shaped for the Calendar dashboard
# ======================================================================

@router.get("/calendar", response_model=CalendarResponse)
def get_calendar(
    source:   list[str] = Query(default=[]),
    status:   list[str] = Query(default=[]),
    min_yw:   Optional[int] = Query(None, description="Min start_year*100+start_week"),
    max_yw:   Optional[int] = Query(None),
    db: Session = Depends(get_db),
) -> CalendarResponse:
    """List proposals shaped as calendar entries. Filter by source(s),
    status(es), and a year-week window. Mirrors PromoCalendar/app.py."""
    return PromoService(db).get_calendar(
        source=source or None,
        status=status or None,
        min_yw=min_yw, max_yw=max_yw,
    )


@router.get("/calendar/conflicts", response_model=CalendarConflictsResponse)
def get_calendar_conflicts(db: Session = Depends(get_db)) -> CalendarConflictsResponse:
    """Find SKU-level conflicts between proposals with overlapping weeks
    AND different sources. Ports the detect_conflicts() algorithm from
    PromoCalendar/promo_data.py verbatim."""
    return PromoService(db).get_calendar_conflicts()


@router.get("/calendar/stats", response_model=CalendarStatsResponse)
def get_calendar_stats(
    source:   list[str] = Query(default=[]),
    status:   list[str] = Query(default=[]),
    min_yw:   Optional[int] = Query(None),
    max_yw:   Optional[int] = Query(None),
    db: Session = Depends(get_db),
) -> CalendarStatsResponse:
    """Aggregate KPIs for the Calendar dashboard."""
    return PromoService(db).get_calendar_stats(
        source=source or None,
        status=status or None,
        min_yw=min_yw, max_yw=max_yw,
    )


# ======================================================================
# Approvals — director actions on proposals
# ======================================================================

@router.get("/calendar/sku-overlaps", response_model=list[PromoSkuOverlap])
def get_sku_overlaps(
    proposal_ids: list[int] = Query(default=[], description="Proposal ids to check for shared SKUs"),
    db: Session = Depends(get_db),
) -> list[PromoSkuOverlap]:
    """SKUs promoted in 2+ of the given proposals (double-promo), each with the
    promos it appears in and the discount in each. Drives the overlap panel
    below the overview table."""
    return PromoService(db).get_sku_overlaps(proposal_ids=proposal_ids)


@router.post("/calendar/approve", response_model=ApprovalActionResponse)
def post_approval(
    action: ApprovalAction,
    db: Session = Depends(get_db),
) -> ApprovalActionResponse:
    """Approve / reject / request revision on a proposal. Writes the
    decision to promo_approvals and updates promo_proposals.status."""
    try:
        result = PromoService(db).perform_approval(
            proposal_id=action.proposal_id,
            decision=action.decision,
            feedback=action.feedback,
            reviewer_id=action.reviewer_id,
        )
        return result   # type: ignore[return-value]
    except ValueError as e:
        from fastapi import HTTPException
        raise HTTPException(status_code=400, detail=str(e))


@router.post("/calendar/acknowledge-conflict")
def post_acknowledge_conflict(
    action: AcknowledgeConflictAction,
    db: Session = Depends(get_db),
) -> dict:
    """Mark a conflict pair as intentionally acknowledged. Appends a log
    line to both proposals + writes an 'acknowledged' approval row."""
    try:
        return PromoService(db).acknowledge_conflict(
            id_a=action.id_a, id_b=action.id_b, reviewer_id=action.reviewer_id,
        )
    except ValueError as e:
        from fastapi import HTTPException
        raise HTTPException(status_code=400, detail=str(e))


@router.put("/proposals/{proposal_id}/status", response_model=StatusChangeResponse)
def put_proposal_status(
    proposal_id: int,
    action: StatusChangeAction,
    db: Session = Depends(get_db),
) -> StatusChangeResponse:
    """Self-service status change (re-submit, withdraw, manual update).
    Used by the CM's My Proposals page to flip 'revision' → 'submitted'
    or 'draft' → 'withdrawn'."""
    try:
        return PromoService(db).change_proposal_status(   # type: ignore[return-value]
            proposal_id=proposal_id,
            new_status=action.status,
            log_line=action.log_line,
            reviewer_id=action.reviewer_id,
        )
    except ValueError as e:
        from fastapi import HTTPException
        raise HTTPException(status_code=400, detail=str(e))


@router.delete("/proposals/{proposal_id}")
def delete_proposal(
    proposal_id: int,
    db: Session = Depends(get_db),
) -> dict:
    """Hard-delete a proposal (cascades to promo_approvals)."""
    try:
        return PromoService(db).delete_proposal(proposal_id=proposal_id)
    except ValueError as e:
        from fastapi import HTTPException
        raise HTTPException(status_code=404, detail=str(e))


@router.get("/proposals/{proposal_id}/sku-detail", response_model=list[PromoSkuDetail])
def get_proposal_sku_detail(
    proposal_id: int,
    db: Session = Depends(get_db),
) -> list[PromoSkuDetail]:
    """Per-SKU detail for a proposal — code, name, regular price, promo
    discount %, implied promo price. Lazy-loaded by the calendar drill-down."""
    return PromoService(db).get_proposal_sku_detail(proposal_id=proposal_id)


@router.get("/proposals/{proposal_id}/approvals", response_model=list[ApprovalRow])
def list_proposal_approvals(
    proposal_id: int,
    db: Session = Depends(get_db),
) -> list[ApprovalRow]:
    """Audit trail of decisions on this proposal."""
    return PromoService(db).list_approvals(proposal_id=proposal_id)
