"""Results: the rollup view and the Scouting Report download.

Gated on the per-project `scout.results.view` permission, so a `lead` sees their
own project's rollup while a `reviewer` on the same project does not.

Client reactions drive the statistics; staff preview rows ride along in the
ratings lists marked `is_staff`, and are excluded from the distributions and
means — a designer's own take is useful context, not client data.
"""

from fastapi import APIRouter, Depends
from fastapi.responses import PlainTextResponse
from sqlalchemy.orm import Session

from app.constants import ASPECT_LABELS, OptionStatus
from app.db import get_db
from app.models.account import Account
from app.models.schemas import (
    AspectTally,
    BiggestSplit,
    FinalPickRow,
    OptionRatingRow,
    OptionResult,
    ResultsOut,
    ReviewerStatus,
)
from app.routers.options import _review_counts, _to_admin_out
from app.services import options as options_service
from app.services import report as report_service
from app.services import reviews as reviews_service
from app.services import levels
from app.services.authz import (
    current_account,
    project_with_permission,
)

router = APIRouter(prefix="/api/projects/{project_id}", tags=["results"])


@router.get("/results", response_model=ResultsOut)
def project_results(
    project_id: int,
    account: Account = Depends(current_account),
    db: Session = Depends(get_db),
) -> ResultsOut:
    project = project_with_permission(db, project_id, account, levels.RESULTS_VIEW)
    data = reviews_service.rollup(db, project.id)
    accounts, is_client = data["accounts"], data["is_client"]
    reviews, votes, picks = data["reviews"], data["votes"], data["picks"]

    all_options = options_service.list_for_admin(db, project.id)
    published_ids = {o.id for o in all_options if o.status == OptionStatus.PUBLISHED}
    review_counts = _review_counts(db, project.id)

    def display(username: str) -> str:
        account = accounts.get(username)
        return account.display_name if account is not None else username

    def level_of(username: str) -> str:
        """The reviewer's level ON THIS PROJECT — what they answered as."""
        return levels.effective_level(username, project.id) or ""

    reviews_by_option: dict[int, list] = {}
    for review in reviews:
        reviews_by_option.setdefault(review.option_id, []).append(review)
    votes_by_option: dict[int, list] = {}
    for vote in votes:
        votes_by_option.setdefault(vote.option_id, []).append(vote)
    picks_by_user = {pick.username: pick for pick in picks}

    # --- reviewers: every member plus anyone who left a reaction -------------
    usernames = set(accounts)
    reviewer_rows: list[ReviewerStatus] = []
    for username in sorted(usernames):
        rated = sum(
            1
            for review in reviews
            if review.username == username
            and review.rating is not None
            and review.option_id in published_ids
        )
        pick = picks_by_user.get(username)
        reviewer_rows.append(
            ReviewerStatus(
                username=username,
                display_name=display(username),
                level=level_of(username),
                is_staff=not is_client(username),
                rated_count=rated,
                total_published=len(published_ids),
                completed_at=pick.completed_at if pick else None,
                has_final_pick=bool(pick and pick.option_id is not None),
            )
        )

    # --- per-option results --------------------------------------------------
    option_results: list[OptionResult] = []
    split: BiggestSplit | None = None
    split_spread = 1  # a split worth calling out needs a spread of at least 2

    for option in all_options:
        option_reviews = reviews_by_option.get(option.id, [])
        rating_rows = [
            OptionRatingRow(
                username=review.username,
                display_name=display(review.username),
                level=level_of(review.username),
                is_staff=not is_client(review.username),
                rating=review.rating,
                note=review.note,
            )
            for review in option_reviews
        ]

        client_rated = [
            review
            for review in option_reviews
            if is_client(review.username) and review.rating is not None
        ]
        distribution = {str(value): 0 for value in range(4)}
        for review in client_rated:
            distribution[str(review.rating)] += 1
        mean = (
            sum(review.rating for review in client_rated) / len(client_rated)
            if client_rated
            else None
        )

        tallies: dict[str, list[int]] = {aspect: [0, 0] for aspect in ASPECT_LABELS}
        for vote in votes_by_option.get(option.id, []):
            if is_client(vote.username):
                tallies[vote.aspect][0 if vote.vote > 0 else 1] += 1
        aspect_tallies = [
            AspectTally(aspect=aspect, up=up, down=down)
            for aspect, (up, down) in tallies.items()
            if up or down
        ]

        final_pick_count = sum(
            1
            for pick in picks
            if pick.option_id == option.id and is_client(pick.username)
        )

        option_results.append(
            OptionResult(
                option=_to_admin_out(option, review_counts.get(option.id, 0)),
                ratings=rating_rows,
                distribution=distribution,
                mean=round(mean, 2) if mean is not None else None,
                aspect_tallies=aspect_tallies,
                final_pick_count=final_pick_count,
            )
        )

        # The largest client disagreement across options (D7: splits are signal).
        if len(client_rated) >= 2:
            high = max(client_rated, key=lambda review: review.rating)
            low = min(client_rated, key=lambda review: review.rating)
            spread = high.rating - low.rating
            if spread > split_spread:
                split_spread = spread
                split = BiggestSplit(
                    option_id=option.id,
                    option_label=option.display_label,
                    high_username=high.username,
                    high_display=display(high.username),
                    high_rating=high.rating,
                    low_username=low.username,
                    low_display=display(low.username),
                    low_rating=low.rating,
                )

    option_labels = {option.id: option.display_label for option in all_options}
    final_pick_rows = [
        FinalPickRow(
            username=pick.username,
            display_name=display(pick.username),
            level=level_of(pick.username),
            is_staff=not is_client(pick.username),
            option_id=pick.option_id,
            option_label=option_labels.get(pick.option_id, "") if pick.option_id else "",
            closing_note=pick.closing_note,
            completed_at=pick.completed_at,
        )
        for pick in picks
    ]

    return ResultsOut(
        reviewers=reviewer_rows,
        options=option_results,
        final_picks=final_pick_rows,
        biggest_split=split,
    )


@router.get("/report", response_class=PlainTextResponse)
def scouting_report(
    project_id: int,
    account: Account = Depends(current_account),
    db: Session = Depends(get_db),
) -> PlainTextResponse:
    """Download the Scouting Report as markdown (D16)."""
    project = project_with_permission(db, project_id, account, levels.RESULTS_VIEW)
    markdown = report_service.build_report(db, project)
    filename = f"scouting-report-{project.slug}.md"
    return PlainTextResponse(
        markdown,
        media_type="text/markdown; charset=utf-8",
        headers={"Content-Disposition": f'attachment; filename="{filename}"'},
    )
