"""Protocols router: voting protocols, formula configurations, and simulation."""

from __future__ import annotations

import uuid

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.database import get_db
from app.engine.smith_threshold import smith_threshold as compute_smith_threshold
from app.engine.techcomm_threshold import techcomm_threshold as compute_techcomm_threshold
from app.engine.threshold import wot_threshold as compute_wot_threshold
from app.models.protocol import FormulaConfig, VotingProtocol
from app.models.user import DuniterIdentity
from app.schemas.protocol import (
    FormulaConfigCreate,
    FormulaConfigOut,
    FormulaConfigUpdate,
    FormulaSimulationRequest,
    FormulaSimulationResult,
    VotingProtocolCreate,
    VotingProtocolOut,
    VotingProtocolUpdate,
)
from app.dependencies.org import get_active_org_id
from app.services.auth_service import get_current_identity

router = APIRouter()


# ── Helpers ─────────────────────────────────────────────────────────────────


async def _get_protocol(
    db: AsyncSession, protocol_id: uuid.UUID, org_id: uuid.UUID | None = None
) -> VotingProtocol:
    """Fetch a voting protocol by ID within the active org scope, or raise 404."""
    stmt = (
        select(VotingProtocol)
        .options(selectinload(VotingProtocol.formula_config))
        .where(VotingProtocol.id == protocol_id)
    )
    if org_id is not None:
        stmt = stmt.where(VotingProtocol.organization_id == org_id)
    else:
        stmt = stmt.where(VotingProtocol.organization_id.is_(None))
    result = await db.execute(stmt)
    protocol = result.scalar_one_or_none()
    if protocol is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Protocole introuvable")
    return protocol


async def _get_formula(db: AsyncSession, formula_id: uuid.UUID) -> FormulaConfig:
    """Fetch a formula config by ID, or raise 404."""
    result = await db.execute(
        select(FormulaConfig).where(FormulaConfig.id == formula_id)
    )
    formula = result.scalar_one_or_none()
    if formula is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Configuration de formule introuvable")
    return formula


# ── Voting Protocol routes ──────────────────────────────────────────────────


@router.get("/", response_model=list[VotingProtocolOut])
async def list_protocols(
    db: AsyncSession = Depends(get_db),
    org_id: uuid.UUID | None = Depends(get_active_org_id),
    vote_type: str | None = Query(default=None, description="Filtrer par type de vote (binary, nuanced)"),
    skip: int = Query(default=0, ge=0),
    limit: int = Query(default=50, ge=1, le=200),
) -> list[VotingProtocolOut]:
    """List all voting protocols with their formula configurations."""
    stmt = select(VotingProtocol).options(selectinload(VotingProtocol.formula_config))

    if org_id is not None:
        stmt = stmt.where(VotingProtocol.organization_id == org_id)
    else:
        stmt = stmt.where(VotingProtocol.organization_id.is_(None))
    if vote_type is not None:
        stmt = stmt.where(VotingProtocol.vote_type == vote_type)

    stmt = stmt.order_by(VotingProtocol.created_at.desc()).offset(skip).limit(limit)
    result = await db.execute(stmt)
    protocols = result.scalars().unique().all()

    return [VotingProtocolOut.model_validate(p) for p in protocols]


@router.post("/", response_model=VotingProtocolOut, status_code=status.HTTP_201_CREATED)
async def create_protocol(
    payload: VotingProtocolCreate,
    db: AsyncSession = Depends(get_db),
    identity: DuniterIdentity = Depends(get_current_identity),
    org_id: uuid.UUID | None = Depends(get_active_org_id),
) -> VotingProtocolOut:
    """Create a new voting protocol.

    The formula_config_id must reference an existing FormulaConfig.
    """
    # Verify formula config exists
    fc_result = await db.execute(
        select(FormulaConfig).where(FormulaConfig.id == payload.formula_config_id)
    )
    if fc_result.scalar_one_or_none() is None:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Configuration de formule introuvable",
        )

    protocol = VotingProtocol(**payload.model_dump(), organization_id=org_id)
    db.add(protocol)
    await db.commit()
    await db.refresh(protocol)

    # Reload with formula config
    protocol = await _get_protocol(db, protocol.id, org_id)
    return VotingProtocolOut.model_validate(protocol)


@router.get("/{id}", response_model=VotingProtocolOut)
async def get_protocol(
    id: uuid.UUID,
    db: AsyncSession = Depends(get_db),
    org_id: uuid.UUID | None = Depends(get_active_org_id),
) -> VotingProtocolOut:
    """Get a single voting protocol with its formula configuration."""
    protocol = await _get_protocol(db, id, org_id)
    return VotingProtocolOut.model_validate(protocol)


@router.put("/{id}", response_model=VotingProtocolOut)
async def update_protocol(
    id: uuid.UUID,
    payload: VotingProtocolUpdate,
    db: AsyncSession = Depends(get_db),
    identity: DuniterIdentity = Depends(get_current_identity),
    org_id: uuid.UUID | None = Depends(get_active_org_id),
) -> VotingProtocolOut:
    """Update a voting protocol (meta-governance).

    Only provided fields will be updated. Requires authentication.
    """
    protocol = await _get_protocol(db, id, org_id)

    update_data = payload.model_dump(exclude_unset=True)
    for field, value in update_data.items():
        setattr(protocol, field, value)

    await db.commit()

    # Reload with formula config
    protocol = await _get_protocol(db, protocol.id, org_id)
    return VotingProtocolOut.model_validate(protocol)


# ── Formula Config routes ───────────────────────────────────────────────────


@router.get("/formulas", response_model=list[FormulaConfigOut])
async def list_formulas(
    db: AsyncSession = Depends(get_db),
    skip: int = Query(default=0, ge=0),
    limit: int = Query(default=50, ge=1, le=200),
) -> list[FormulaConfigOut]:
    """List all formula configurations."""
    stmt = (
        select(FormulaConfig)
        .order_by(FormulaConfig.created_at.desc())
        .offset(skip)
        .limit(limit)
    )
    result = await db.execute(stmt)
    formulas = result.scalars().all()

    return [FormulaConfigOut.model_validate(f) for f in formulas]


@router.post("/formulas", response_model=FormulaConfigOut, status_code=status.HTTP_201_CREATED)
async def create_formula(
    payload: FormulaConfigCreate,
    db: AsyncSession = Depends(get_db),
    identity: DuniterIdentity = Depends(get_current_identity),
) -> FormulaConfigOut:
    """Create a new formula configuration for WoT threshold computation."""
    formula = FormulaConfig(**payload.model_dump())
    db.add(formula)
    await db.commit()
    await db.refresh(formula)

    return FormulaConfigOut.model_validate(formula)


@router.get("/formulas/{id}", response_model=FormulaConfigOut)
async def get_formula(
    id: uuid.UUID,
    db: AsyncSession = Depends(get_db),
) -> FormulaConfigOut:
    """Get a single formula configuration."""
    formula = await _get_formula(db, id)
    return FormulaConfigOut.model_validate(formula)


@router.put("/formulas/{id}", response_model=FormulaConfigOut)
async def update_formula(
    id: uuid.UUID,
    payload: FormulaConfigUpdate,
    db: AsyncSession = Depends(get_db),
    identity: DuniterIdentity = Depends(get_current_identity),
) -> FormulaConfigOut:
    """Update a formula configuration (meta-governance).

    Only provided fields will be updated. Requires authentication.
    """
    formula = await _get_formula(db, id)

    update_data = payload.model_dump(exclude_unset=True)
    for field, value in update_data.items():
        setattr(formula, field, value)

    await db.commit()
    await db.refresh(formula)

    return FormulaConfigOut.model_validate(formula)


# ── Simulation ──────────────────────────────────────────────────────────────


@router.post("/simulate", response_model=FormulaSimulationResult)
async def simulate_formula(
    payload: FormulaSimulationRequest,
) -> FormulaSimulationResult:
    """Simulate a WoT threshold formula computation.

    Pure calculation endpoint -- no database access needed.
    Accepts WoT size, total votes, and formula parameters,
    returns the computed thresholds and derived values.
    """
    # Compute WoT threshold
    wot_thresh = compute_wot_threshold(
        wot_size=payload.wot_size,
        total_votes=payload.total_votes,
        majority_pct=payload.majority_pct,
        base_exponent=payload.base_exponent,
        gradient_exponent=payload.gradient_exponent,
        constant_base=payload.constant_base,
    )

    # Compute derived values for transparency
    w = payload.wot_size
    t = payload.total_votes
    m = payload.majority_pct / 100.0
    g = payload.gradient_exponent

    participation_rate = t / w if w > 0 else 0.0
    turnout_ratio = min(t / w, 1.0) if w > 0 else 0.0
    inertia_factor = 1.0 - turnout_ratio ** g if t > 0 else 1.0
    required_ratio = m + (1.0 - m) * inertia_factor

    # Smith threshold (optional)
    smith_thresh = None
    if payload.smith_wot_size is not None and payload.smith_exponent is not None:
        smith_thresh = compute_smith_threshold(payload.smith_wot_size, payload.smith_exponent)

    # TechComm threshold (optional)
    techcomm_thresh = None
    if payload.techcomm_size is not None and payload.techcomm_exponent is not None:
        techcomm_thresh = compute_techcomm_threshold(payload.techcomm_size, payload.techcomm_exponent)

    return FormulaSimulationResult(
        wot_threshold=wot_thresh,
        smith_threshold=smith_thresh,
        techcomm_threshold=techcomm_thresh,
        participation_rate=round(participation_rate, 6),
        required_ratio=round(required_ratio, 6),
        inertia_factor=round(inertia_factor, 6),
    )
