"""Decision service: step advancement logic and vote session integration."""

from __future__ import annotations

import uuid
from datetime import datetime, timedelta, timezone

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.models.decision import Decision, DecisionStep
from app.models.protocol import VotingProtocol, FormulaConfig
from app.models.vote import VoteSession


# Valid status transitions for decisions
_DECISION_STATUS_ORDER = [
    "draft",
    "qualification",
    "review",
    "voting",
    "executed",
    "closed",
]


async def advance_decision(decision_id: uuid.UUID, db: AsyncSession) -> Decision:
    """Move a decision to its next step.

    Completes the current active step and activates the next pending step.
    If no more steps remain, the decision status advances to the next phase.

    Parameters
    ----------
    decision_id:
        UUID of the Decision to advance.
    db:
        Async database session.

    Returns
    -------
    Decision
        The updated decision.

    Raises
    ------
    ValueError
        If the decision is not found, or no further advancement is possible.
    """
    result = await db.execute(
        select(Decision)
        .options(selectinload(Decision.steps))
        .where(Decision.id == decision_id)
    )
    decision = result.scalar_one_or_none()
    if decision is None:
        raise ValueError(f"Decision introuvable : {decision_id}")

    if decision.status == "closed":
        raise ValueError("La decision est deja cloturee")

    steps: list[DecisionStep] = sorted(decision.steps, key=lambda s: s.step_order)

    # Find the current active step
    active_step: DecisionStep | None = None
    for step in steps:
        if step.status == "active":
            active_step = step
            break

    if active_step is not None:
        # Complete the active step
        active_step.status = "completed"

        # Activate the next pending step
        next_step: DecisionStep | None = None
        for step in steps:
            if step.step_order > active_step.step_order and step.status == "pending":
                next_step = step
                break

        if next_step is not None:
            next_step.status = "active"
        else:
            # No more steps: advance the decision status
            _advance_decision_status(decision)
    else:
        # No active step: try to activate the first pending step
        first_pending: DecisionStep | None = None
        for step in steps:
            if step.status == "pending":
                first_pending = step
                break

        if first_pending is not None:
            first_pending.status = "active"
            # Also advance decision out of draft if needed
            if decision.status == "draft":
                decision.status = "qualification"
        else:
            # All steps are completed: advance the decision status
            _advance_decision_status(decision)

    await db.commit()
    await db.refresh(decision)

    return decision


def _advance_decision_status(decision: Decision) -> None:
    """Move a decision to its next status in the lifecycle."""
    try:
        current_index = _DECISION_STATUS_ORDER.index(decision.status)
    except ValueError:
        return

    next_index = current_index + 1
    if next_index < len(_DECISION_STATUS_ORDER):
        decision.status = _DECISION_STATUS_ORDER[next_index]


async def create_vote_session_for_step(
    decision_id: uuid.UUID,
    step_id: uuid.UUID,
    db: AsyncSession,
) -> VoteSession:
    """Create a VoteSession linked to a decision step.

    Uses the decision's voting_protocol to configure the session.
    Raises ValueError if the decision or step is not found, the step
    already has a vote session, or no voting protocol is configured.

    Parameters
    ----------
    decision_id:
        UUID of the Decision.
    step_id:
        UUID of the DecisionStep to attach the vote session to.
    db:
        Async database session.

    Returns
    -------
    VoteSession
        The newly created vote session.
    """
    # Fetch decision with steps
    result = await db.execute(
        select(Decision)
        .options(selectinload(Decision.steps))
        .where(Decision.id == decision_id)
    )
    decision = result.scalar_one_or_none()
    if decision is None:
        raise ValueError(f"Decision introuvable : {decision_id}")

    # Find the step
    step: DecisionStep | None = None
    for s in decision.steps:
        if s.id == step_id:
            step = s
            break

    if step is None:
        raise ValueError(f"Etape introuvable : {step_id}")

    # Check step doesn't already have a vote session
    if step.vote_session_id is not None:
        raise ValueError("Cette etape possede deja une session de vote")

    # Check voting protocol is configured
    if decision.voting_protocol_id is None:
        raise ValueError("Aucun protocole de vote configure pour cette decision")

    # Fetch the voting protocol and its formula config for duration
    proto_result = await db.execute(
        select(VotingProtocol)
        .options(selectinload(VotingProtocol.formula_config))
        .where(VotingProtocol.id == decision.voting_protocol_id)
    )
    protocol = proto_result.scalar_one_or_none()
    if protocol is None:
        raise ValueError("Protocole de vote introuvable")

    duration_days = protocol.formula_config.duration_days if protocol.formula_config else 30
    now = datetime.now(timezone.utc)

    # Create the vote session with explicit UUID (needed before flush)
    session_id = uuid.uuid4()
    session = VoteSession(
        id=session_id,
        decision_id=decision.id,
        voting_protocol_id=protocol.id,
        starts_at=now,
        ends_at=now + timedelta(days=duration_days),
        status="open",
    )
    db.add(session)

    # Link session to step
    step.vote_session_id = session_id

    await db.commit()
    await db.refresh(session)

    return session
