from __future__ import annotations

import asyncio
import uuid
from typing import Any

from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.encoders import jsonable_encoder
from sqlalchemy.ext.asyncio import AsyncSession

from api.database import get_session
from api.events import EventEnvelope, publish_event
from api.models.repository_rename import RepositoryRenameOperation
from api.schemas.repository_rename import (
    ForgeIdentityVerifyRequest,
    RepositoryRenameOperationCreate,
    RepositoryRenameOperationRead,
    RepositoryRenamePhaseApply,
    RepositoryRenamePreflightRead,
    RepositoryRenamePreflightRequest,
    RepositoryRenameRollbackPreflightRead,
    RepositoryRenameRollbackRequest,
    RepositoryRenameVerificationRead,
)
from api.services.forge_repository import (
    ForgeRepositoryGateway,
    get_forge_repository_gateway,
)
from api.services.repository_rename import (
    RenameLifecycleError,
    apply_phase,
    apply_rollback,
    build_preflight,
    create_operation,
    list_operations,
    load_operation,
    load_operation_by_id,
    rollback_preflight,
    verify_forge_identity,
    verify_operation,
)


router = APIRouter(prefix="/repos/{repo_id}", tags=["repository-renames"])
operation_router = APIRouter(
    prefix="/repository-renames", tags=["repository-renames"]
)


def repository_rename_events(
    operation: RepositoryRenameOperation,
    *,
    outcome: str,
    no_op: bool = False,
    verification: dict[str, Any] | None = None,
) -> list[tuple[str, EventEnvelope]]:
    """Build credential-free lifecycle events from the durable journal."""
    telemetry = (operation.evidence or {}).get("telemetry") or {}
    phase_durations = telemetry.get("phase_durations_ms") or {}
    attributes = {
        "operation_id": str(operation.id),
        "repo_id": str(operation.repo_id),
        "forge_repository_id": operation.expected_forge_repository_id,
        "old_slug": operation.old_slug,
        "new_slug": operation.new_slug,
        "phase": operation.phase,
        "actor": operation.actor,
        "expected_source_commit": operation.expected_source_commit,
        "outcome": outcome,
        "no_op": no_op,
        "phase_duration_ms": int(phase_durations.get(operation.phase) or 0),
        "retries": int(telemetry.get("retries") or 0),
        "failures": int(telemetry.get("failures") or 0),
        "rollback_attempts": int(telemetry.get("rollback_attempts") or 0),
        "rollback_outcome": telemetry.get("rollback_outcome"),
        "verification_outcome": (
            "passed" if verification and verification.get("ok") else
            "failed" if verification else
            telemetry.get("verification_outcome", "pending")
        ),
        "error_code": operation.error_code if outcome == "failed" else None,
        "evidence_ref": f"repository-renames/operations/{operation.id}",
    }
    subject = (
        "org.statehub.repo.rename.failed"
        if outcome == "failed"
        else "org.statehub.repo.rename.verified"
        if verification is not None
        else "org.statehub.repo.rename.rolled_back"
        if operation.phase == "rolled-back"
        else "org.statehub.repo.rename.phase"
    )
    events = [(subject, EventEnvelope.new(subject, attributes=attributes))]
    if operation.phase == "completed" and outcome == "succeeded":
        renamed_subject = "org.statehub.repo.renamed"
        events.append(
            (renamed_subject, EventEnvelope.new(renamed_subject, attributes=attributes))
        )
    return events


def _publish_rename_events(
    operation: RepositoryRenameOperation,
    *,
    outcome: str,
    no_op: bool = False,
    verification: dict[str, Any] | None = None,
) -> None:
    for subject, envelope in repository_rename_events(
        operation, outcome=outcome, no_op=no_op, verification=verification
    ):
        asyncio.create_task(publish_event(subject, envelope))


def _raise(exc: RenameLifecycleError) -> None:
    raise HTTPException(
        status_code=exc.status_code,
        detail=jsonable_encoder(
            {"code": exc.code, "message": str(exc), "details": exc.details}
        ),
    ) from exc


def _operation_read(operation: RepositoryRenameOperation, *, no_op: bool = False) -> dict[str, Any]:
    return {
        "id": operation.id,
        "repo_id": operation.repo_id,
        "phase": operation.phase,
        "old_slug": operation.old_slug,
        "new_slug": operation.new_slug,
        "expected_forge_repository_id": operation.expected_forge_repository_id,
        "expected_source_commit": operation.expected_source_commit,
        "expected_default_branch": operation.expected_default_branch,
        "actor": operation.actor,
        "phase_changed_at": operation.phase_changed_at,
        "preflighted_at": operation.preflighted_at,
        "preflight_expires_at": operation.preflight_expires_at,
        "completed_at": operation.completed_at,
        "rolled_back_at": operation.rolled_back_at,
        "evidence": operation.evidence,
        "error_code": operation.error_code,
        "error_message": operation.error_message,
        "error_details": operation.error_details,
        "error_at": operation.error_at,
        "no_op": no_op,
    }


@router.post("/forge-identity/verify")
async def verify_repository_forge_identity(
    repo_id: uuid.UUID,
    body: ForgeIdentityVerifyRequest,
    session: AsyncSession = Depends(get_session),
    gateway: ForgeRepositoryGateway = Depends(get_forge_repository_gateway),
) -> dict[str, Any]:
    try:
        identity = await verify_forge_identity(session, gateway, repo_id, body)
    except RenameLifecycleError as exc:
        _raise(exc)
    return {
        "id": identity.id,
        "repo_id": identity.repo_id,
        "provider": identity.provider,
        "forge_instance": identity.forge_instance,
        "forge_owner": identity.forge_owner,
        "forge_repository_id": identity.forge_repository_id,
        "verification_state": identity.verification_state,
        "verified_at": identity.verified_at,
        "verified_by": identity.verified_by,
        "verification_evidence": identity.verification_evidence,
    }


@router.post("/rename/preflight", response_model=RepositoryRenamePreflightRead)
async def repository_rename_preflight(
    repo_id: uuid.UUID,
    body: RepositoryRenamePreflightRequest,
    session: AsyncSession = Depends(get_session),
    gateway: ForgeRepositoryGateway = Depends(get_forge_repository_gateway),
) -> dict[str, Any]:
    try:
        return await build_preflight(session, gateway, repo_id, body)
    except RenameLifecycleError as exc:
        _raise(exc)


@router.post(
    "/rename/operations",
    response_model=RepositoryRenameOperationRead,
    status_code=status.HTTP_201_CREATED,
)
async def create_repository_rename_operation(
    repo_id: uuid.UUID,
    body: RepositoryRenameOperationCreate,
    session: AsyncSession = Depends(get_session),
    gateway: ForgeRepositoryGateway = Depends(get_forge_repository_gateway),
) -> dict[str, Any]:
    try:
        operation, no_op = await create_operation(session, gateway, repo_id, body)
    except RenameLifecycleError as exc:
        _raise(exc)
    _publish_rename_events(operation, outcome="succeeded", no_op=no_op)
    return _operation_read(operation, no_op=no_op)


@operation_router.get(
    "/operations/{operation_id}", response_model=RepositoryRenameOperationRead
)
async def get_repository_rename_operation_by_id(
    operation_id: uuid.UUID,
    session: AsyncSession = Depends(get_session),
) -> dict[str, Any]:
    """Resolve an operation journal without requiring its repository UUID."""
    try:
        operation = await load_operation_by_id(session, operation_id)
    except RenameLifecycleError as exc:
        _raise(exc)
    return _operation_read(operation)


@router.get(
    "/rename/operations", response_model=list[RepositoryRenameOperationRead]
)
async def list_repository_rename_operations(
    repo_id: uuid.UUID,
    active_only: bool = False,
    session: AsyncSession = Depends(get_session),
) -> list[dict[str, Any]]:
    try:
        operations = await list_operations(session, repo_id, active_only=active_only)
    except RenameLifecycleError as exc:
        _raise(exc)
    return [_operation_read(operation) for operation in operations]


@router.get(
    "/rename/operations/{operation_id}", response_model=RepositoryRenameOperationRead
)
async def get_repository_rename_operation(
    repo_id: uuid.UUID,
    operation_id: uuid.UUID,
    session: AsyncSession = Depends(get_session),
) -> dict[str, Any]:
    try:
        operation = await load_operation(session, repo_id, operation_id)
    except RenameLifecycleError as exc:
        _raise(exc)
    return _operation_read(operation)


@router.post(
    "/rename/operations/{operation_id}/phases/{phase}",
    response_model=RepositoryRenameOperationRead,
)
async def apply_repository_rename_phase(
    repo_id: uuid.UUID,
    operation_id: uuid.UUID,
    phase: str,
    body: RepositoryRenamePhaseApply,
    session: AsyncSession = Depends(get_session),
    gateway: ForgeRepositoryGateway = Depends(get_forge_repository_gateway),
) -> dict[str, Any]:
    try:
        operation, no_op = await apply_phase(
            session, gateway, repo_id, operation_id, phase, body
        )
    except RenameLifecycleError as exc:
        failed = await session.get(RepositoryRenameOperation, operation_id)
        if failed is not None:
            _publish_rename_events(failed, outcome="failed")
        _raise(exc)
    _publish_rename_events(operation, outcome="succeeded", no_op=no_op)
    return _operation_read(operation, no_op=no_op)


@router.get(
    "/rename/operations/{operation_id}/verify",
    response_model=RepositoryRenameVerificationRead,
)
async def verify_repository_rename_operation(
    repo_id: uuid.UUID,
    operation_id: uuid.UUID,
    session: AsyncSession = Depends(get_session),
    gateway: ForgeRepositoryGateway = Depends(get_forge_repository_gateway),
) -> dict[str, Any]:
    try:
        operation = await load_operation(session, repo_id, operation_id)
        verification = await verify_operation(session, gateway, operation)
    except RenameLifecycleError as exc:
        _raise(exc)
    _publish_rename_events(
        operation,
        outcome="succeeded" if verification["ok"] else "failed",
        verification=verification,
    )
    return verification


@router.post(
    "/rename/operations/{operation_id}/rollback-preflight",
    response_model=RepositoryRenameRollbackPreflightRead,
)
async def preflight_repository_rename_rollback(
    repo_id: uuid.UUID,
    operation_id: uuid.UUID,
    body: RepositoryRenameRollbackRequest,
    session: AsyncSession = Depends(get_session),
    gateway: ForgeRepositoryGateway = Depends(get_forge_repository_gateway),
) -> dict[str, Any]:
    try:
        operation, report = await rollback_preflight(
            session,
            gateway,
            repo_id,
            operation_id,
            expected_phase=body.expected_phase,
            confirmation=body.confirmation,
        )
    except RenameLifecycleError as exc:
        failed = await session.get(RepositoryRenameOperation, operation_id)
        if failed is not None:
            _publish_rename_events(failed, outcome="failed")
        _raise(exc)
    _publish_rename_events(operation, outcome="succeeded")
    return {
        "operation_id": operation.id,
        "repo_id": operation.repo_id,
        **report,
        "operation": _operation_read(operation),
    }


@router.post(
    "/rename/operations/{operation_id}/rollback",
    response_model=RepositoryRenameOperationRead,
)
async def rollback_repository_rename_operation(
    repo_id: uuid.UUID,
    operation_id: uuid.UUID,
    body: RepositoryRenameRollbackRequest,
    session: AsyncSession = Depends(get_session),
    gateway: ForgeRepositoryGateway = Depends(get_forge_repository_gateway),
) -> dict[str, Any]:
    try:
        operation, no_op = await apply_rollback(
            session,
            gateway,
            repo_id,
            operation_id,
            expected_phase=body.expected_phase,
            confirmation=body.confirmation,
        )
    except RenameLifecycleError as exc:
        failed = await session.get(RepositoryRenameOperation, operation_id)
        if failed is not None:
            _publish_rename_events(failed, outcome="failed")
        _raise(exc)
    _publish_rename_events(operation, outcome="succeeded", no_op=no_op)
    return _operation_read(operation, no_op=no_op)
