"""Connection-application workflow: status transitions and installation hand-off."""
from fastapi import HTTPException, status as http_status
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.core.time import today_local, utcnow
from app.models.application import ApplicationStatusHistory, ConnectionApplication
from app.models.customer import Connection, Customer
from app.models.enums import APPLICATION_TRANSITIONS, ApplicationStatus, ConnectionStatus
from app.models.user import User
from app.schemas.application import ApplicationStatusChange
from app.services.numbering import next_number


def allowed_transitions(current: ApplicationStatus) -> list[ApplicationStatus]:
    return sorted(APPLICATION_TRANSITIONS[current], key=lambda s: s.value)


def record_history(
    db: Session,
    application: ConnectionApplication,
    *,
    from_status: ApplicationStatus | None,
    to_status: ApplicationStatus,
    user_id: int | None,
    remarks: str | None = None,
) -> ApplicationStatusHistory:
    entry = ApplicationStatusHistory(
        application_id=application.id,
        from_status=from_status,
        to_status=to_status,
        changed_by_id=user_id,
        changed_at=utcnow(),
        remarks=remarks,
    )
    db.add(entry)
    return entry


def _find_or_create_customer(db: Session, application: ConnectionApplication) -> Customer:
    """Reuse an existing customer with the same phone, otherwise create one.

    Phone is the practical identity key here: NID is optional in the field, and
    re-applying households are common.
    """
    existing = db.scalar(
        select(Customer).where(
            Customer.phone == application.phone, Customer.is_deleted.is_(False)
        )
    )
    if existing is not None:
        return existing

    customer = Customer(
        customer_code=next_number(db, "CUS"),
        full_name=application.applicant_name,
        phone=application.phone,
        alt_phone=application.alt_phone,
        nid=application.nid,
        email=application.email,
        address=application.address,
        area_id=application.area_id,
    )
    db.add(customer)
    db.flush()
    return customer


def _install(
    db: Session, application: ConnectionApplication, payload: ApplicationStatusChange
) -> Connection:
    """Promote an approved application into a live customer + connection."""
    if application.connection is not None and not application.connection.is_deleted:
        raise HTTPException(
            status_code=http_status.HTTP_409_CONFLICT,
            detail=f"Application already installed as {application.connection.connection_code}",
        )

    customer = _find_or_create_customer(db, application)
    connection = Connection(
        connection_code=next_number(db, "CON"),
        customer_id=customer.id,
        application_id=application.id,
        area_id=application.area_id,
        status=ConnectionStatus.ACTIVE,
        activated_on=payload.activated_on or today_local(),
        monthly_bill=payload.monthly_bill,
        installed_by_id=application.assigned_technician_id,
    )
    db.add(connection)
    db.flush()
    return connection


def change_status(
    db: Session,
    application: ConnectionApplication,
    payload: ApplicationStatusChange,
    actor: User,
) -> Connection | None:
    """Apply a status change, enforcing the workflow. Returns a Connection on install."""
    current = application.status
    target = payload.status

    if target == current:
        raise HTTPException(
            status_code=http_status.HTTP_400_BAD_REQUEST,
            detail=f"Application is already '{current.value}'",
        )
    if target not in APPLICATION_TRANSITIONS[current]:
        allowed = ", ".join(s.value for s in allowed_transitions(current)) or "none"
        raise HTTPException(
            status_code=http_status.HTTP_400_BAD_REQUEST,
            detail=f"Cannot move from '{current.value}' to '{target.value}'. Allowed: {allowed}",
        )
    if target == ApplicationStatus.REJECTED and not (payload.rejection_reason or "").strip():
        raise HTTPException(
            status_code=http_status.HTTP_400_BAD_REQUEST,
            detail="A rejection reason is required",
        )

    now = utcnow()
    connection: Connection | None = None

    if target == ApplicationStatus.SURVEY:
        application.survey_date = payload.survey_date or application.survey_date or today_local()
        application.surveyed_at = now
    elif target == ApplicationStatus.APPROVED:
        application.approved_at = now
    elif target == ApplicationStatus.INSTALLED:
        connection = _install(db, application, payload)
        application.installed_at = now
    elif target == ApplicationStatus.REJECTED:
        application.rejected_at = now
        application.rejection_reason = payload.rejection_reason.strip()
    elif target == ApplicationStatus.PENDING:
        # Reopening: clear the outcome so the record does not read as both live and closed.
        application.rejected_at = None
        application.rejection_reason = None

    application.status = target
    record_history(
        db,
        application,
        from_status=current,
        to_status=target,
        user_id=actor.id,
        remarks=payload.remarks,
    )
    return connection
