"""EnergyContract CRUD, versioning, and pricing-profile API (M6-T04).

All endpoints are under /api/energy, require an authenticated session, and
write endpoints (POST/PATCH) additionally require a non-empty X-CSRF-Token
header.

There is deliberately no DELETE endpoint for contracts: the FK RESTRICT
constraint on energy_contract_version.contract_id and
energy_cost_period.contract_version_id prevents accidental deletion of
contracts that have billing history.  Users should instead deactivate a
contract (PATCH active=false) to stop it from being used.

Route ordering note
-------------------
GET /api/energy/profiles and GET /api/energy/contracts/{id} are on separate
path prefixes (/energy/profiles vs /energy/contracts/{id}) so there is no
ambiguity even without extra ordering gymnastics.
"""

from __future__ import annotations

import logging
from datetime import UTC, datetime
from typing import Any

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session

from app.api.routes.api.deps import require_csrf, require_session
from app.dependencies import get_db
from app.integrations.pricing.profiles import (
    ProfileNotFoundError,
    ProfileValidationError,
    list_profiles,
)
from app.schemas.energy_contract import (
    ContractCreate,
    ContractDetailResponse,
    ContractListResponse,
    ContractPatch,
    ContractResponse,
    ContractVersionResponse,
    ProfilesResponse,
    VersionCreate,
)
from app.services.auth import AuthenticatedSession
from app.services import timezone as _tz_mod
from app.services.contracts import (
    ContractVersionError,
    activate_contract,
    add_version,
    create_contract,
    deactivate_contract,
    get_contract_or_none,
    list_contracts,
)

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/energy", tags=["api-energy-contracts"])


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _get_contract_or_404(db: Session, contract_id: int):
    """Return the contract with the given id or raise 404."""
    contract = get_contract_or_none(db, contract_id)
    if contract is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Energy contract {contract_id!r} not found.",
        )
    return contract


def _contract_detail(db: Session, contract) -> ContractDetailResponse:
    """Build a ContractDetailResponse for *contract*, loading versions."""
    # Eager-load versions ordered by effective_from for consistent display.
    from sqlalchemy import select

    from app.models.energy import EnergyContractVersion

    versions = list(
        db.execute(
            select(EnergyContractVersion)
            .where(EnergyContractVersion.contract_id == contract.id)
            .order_by(EnergyContractVersion.effective_from)
        )
        .scalars()
        .all()
    )

    version_schemas = [ContractVersionResponse.model_validate(v) for v in versions]
    return ContractDetailResponse(
        id=contract.id,
        name=contract.name,
        kind=contract.kind,
        active=contract.active,
        currency=contract.currency,
        created_at=contract.created_at,
        updated_at=contract.updated_at,
        versions=version_schemas,
    )


def _raise_422_for_profile_error(exc: Exception) -> Any:
    """Convert a ProfileValidationError or ProfileNotFoundError into HTTP 422."""
    raise HTTPException(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        detail=str(exc),
    )


def _localize_effective_from(dt: datetime | None) -> datetime:
    """Resolve *dt* to an aware UTC datetime for storage.

    Rules (Principle A):
    - If *dt* is None → use ``datetime.now(UTC)`` (unchanged from before).
    - If *dt* is timezone-aware → convert to UTC as-is.
    - If *dt* is timezone-naive → interpret as server local wall-clock time,
      localize with ``local_tz()``, then convert to UTC.

    This means a front-end that sends ``"2026-06-25T00:00:00"`` (no Z) has it
    interpreted as local midnight (e.g. CEST = UTC+2 → stored as 2026-06-24T22:00:00Z),
    not as UTC midnight.
    """
    if dt is None:
        return datetime.now(UTC)
    if dt.tzinfo is not None:
        # Already aware: convert to UTC.
        return dt.astimezone(UTC)
    # Naive: assume server local wall-clock.
    tz = _tz_mod.local_tz()
    local_dt = dt.replace(tzinfo=tz)
    return local_dt.astimezone(UTC)


# ---------------------------------------------------------------------------
# GET /api/energy/profiles
# ---------------------------------------------------------------------------


@router.get("/profiles", response_model=ProfilesResponse)
def get_profiles(
    _auth: AuthenticatedSession = Depends(require_session),
) -> ProfilesResponse:
    """List all available pricing profile structures.

    Returns the full profile structure for each supported contract kind
    (``manual`` and ``tibber``).  The front-end uses this to dynamically
    render the correct fields and labels for the contract creation/editing form.
    """
    return ProfilesResponse(profiles=list_profiles())


# ---------------------------------------------------------------------------
# GET /api/energy/contracts
# ---------------------------------------------------------------------------


@router.get("/contracts", response_model=ContractListResponse)
def list_energy_contracts(
    db: Session = Depends(get_db),
    _auth: AuthenticatedSession = Depends(require_session),
) -> ContractListResponse:
    """List all energy contracts with their active status.

    Returns a flat list (no embedded version history); use
    GET /api/energy/contracts/{id} to fetch the full version history for a
    specific contract.
    """
    contracts = list_contracts(db)
    items = [ContractResponse.model_validate(c) for c in contracts]
    return ContractListResponse(items=items, total=len(items))


# ---------------------------------------------------------------------------
# POST /api/energy/contracts
# ---------------------------------------------------------------------------


@router.post(
    "/contracts",
    response_model=ContractDetailResponse,
    status_code=status.HTTP_201_CREATED,
)
def create_energy_contract(
    body: ContractCreate,
    db: Session = Depends(get_db),
    _auth: AuthenticatedSession = Depends(require_session),
    _csrf: None = Depends(require_csrf),
) -> ContractDetailResponse:
    """Create a new energy contract with an initial pricing version.

    The ``values`` dict is validated against the YAML profile for the given
    ``kind``; non-conforming values result in 422 Unprocessable Entity.
    The new contract is created with ``active=False``; use
    PATCH /api/energy/contracts/{id} with ``active=true`` to activate it.
    """
    effective_from = _localize_effective_from(body.effective_from)

    try:
        contract = create_contract(
            db,
            name=body.name,
            kind=body.kind,
            currency=body.currency,
            values=body.values,
            effective_from=effective_from,
        )
    except (ProfileNotFoundError, ProfileValidationError) as exc:
        _raise_422_for_profile_error(exc)

    db.commit()
    db.refresh(contract)
    logger.info("Created energy contract id=%d name=%r kind=%s", contract.id, contract.name, contract.kind)
    return _contract_detail(db, contract)


# ---------------------------------------------------------------------------
# GET /api/energy/contracts/{id}
# ---------------------------------------------------------------------------


@router.get("/contracts/{contract_id}", response_model=ContractDetailResponse)
def get_energy_contract(
    contract_id: int,
    db: Session = Depends(get_db),
    _auth: AuthenticatedSession = Depends(require_session),
) -> ContractDetailResponse:
    """Return a single energy contract with its full version history.

    Versions are ordered by ``effective_from`` ascending so the caller can
    easily inspect the pricing timeline.
    """
    contract = _get_contract_or_404(db, contract_id)
    return _contract_detail(db, contract)


# ---------------------------------------------------------------------------
# PATCH /api/energy/contracts/{id}
# ---------------------------------------------------------------------------


@router.patch("/contracts/{contract_id}", response_model=ContractDetailResponse)
def patch_energy_contract(
    contract_id: int,
    body: ContractPatch,
    db: Session = Depends(get_db),
    _auth: AuthenticatedSession = Depends(require_session),
    _csrf: None = Depends(require_csrf),
) -> ContractDetailResponse:
    """Partially update a contract: rename or change activation status.

    - ``name``: updates the human-readable label.
    - ``active=true``: activates this contract (all others are deactivated).
    - ``active=false``: deactivates this contract (no effect on others).

    At most one contract may be active at any time; the service layer enforces
    mutual exclusion.
    """
    contract = _get_contract_or_404(db, contract_id)

    if body.name is not None:
        contract.name = body.name
        contract.updated_at = datetime.now(UTC)

    if body.active is True:
        activate_contract(db, contract)
    elif body.active is False:
        deactivate_contract(db, contract)

    db.commit()
    db.refresh(contract)
    return _contract_detail(db, contract)


# ---------------------------------------------------------------------------
# POST /api/energy/contracts/{id}/versions
# ---------------------------------------------------------------------------


@router.post(
    "/contracts/{contract_id}/versions",
    response_model=ContractDetailResponse,
    status_code=status.HTTP_201_CREATED,
)
def add_contract_version(
    contract_id: int,
    body: VersionCreate,
    db: Session = Depends(get_db),
    _auth: AuthenticatedSession = Depends(require_session),
    _csrf: None = Depends(require_csrf),
) -> ContractDetailResponse:
    """Add a new pricing version to an existing contract.

    This is how price changes are recorded: the current open version is
    automatically closed (its ``effective_to`` is set to ``body.effective_from``)
    and a new version is created starting at ``body.effective_from``.

    The ``values`` dict must conform to the contract's pricing profile.
    Non-conforming values return 422.  If ``effective_from`` is not strictly
    after the previous version's ``effective_from``, 422 is returned without
    writing any rows.

    Historical versions are never modified; this endpoint is append-only.
    """
    contract = _get_contract_or_404(db, contract_id)
    effective_from = _localize_effective_from(body.effective_from)

    try:
        add_version(
            db,
            contract,
            effective_from=effective_from,
            values=body.values,
        )
    except ContractVersionError as exc:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=str(exc),
        )
    except (ProfileNotFoundError, ProfileValidationError) as exc:
        _raise_422_for_profile_error(exc)

    db.commit()
    db.refresh(contract)
    return _contract_detail(db, contract)
