"""Deployments domain - FastAPI router."""
from uuid import UUID

from fastapi import APIRouter, Depends, Path, Response
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.exc import IntegrityError

from src.core.database import get_db
from src.core.dependencies import add_deprecation_header
from src.core.dependencies import (
    require_active_billing_for_deploy,
    require_active_billing_for_project,
)
from src.core.exceptions import ConflictError, NotFoundError
from src.domain.auth.models import User
from src.domain.auth.router import get_current_user
from src.domain.deployments.models import ProjectDomain
from src.domain.deployments.repository import (
    DeployRepository,
    DeploymentTargetRepository,
    DomainRepository,
)
from src.domain.deployments.schemas import (
    DeployCreate,
    DeployResponse,
    DomainCreate,
    DomainResponse,
)
from src.domain.deployments.service import (
    DeployService,
    domain_response as _domain_response_fn,
)
from src.domain.projects.repository import ProjectRepository
from src.domain.revisions.repository import RevisionRepository
from src.workers.enqueue import enqueue_run_project_deploy

router = APIRouter(tags=["deployments"])


async def get_deploy_service(
    db: AsyncSession = Depends(get_db),
) -> DeployService:
    return DeployService(
        deploy_repo=DeployRepository(db),
        domain_repo=DomainRepository(db),
        target_repo=DeploymentTargetRepository(db),
        project_repo=ProjectRepository(db),
        revision_repo=RevisionRepository(db),
    )


@router.post(
    "/api/projects/{project}/deploys", response_model=DeployResponse
)
async def create_deploy(
    body: DeployCreate,
    project: UUID = Depends(require_active_billing_for_project),
    user: User = Depends(get_current_user),
    service: DeployService = Depends(get_deploy_service),
    db: AsyncSession = Depends(get_db),
):
    revision_id = body.revision_id
    if revision_id is None:
        latest = await RevisionRepository(db).get_latest(project)
        if not latest:
            raise NotFoundError("No revision found for this project")
        revision_id = latest.id
    revision = await RevisionRepository(db).get_by_id(revision_id)
    if not revision or revision.project_id != project:
        raise NotFoundError("Revision not found for this project")

    try:
        d = await service.create_deploy(
            project, revision_id, body.environment, user.id
        )
        await db.commit()
    except IntegrityError as exc:
        await db.rollback()
        if "uq_project_deploy_active" not in str(exc.orig):
            raise
        raise ConflictError(
            f"A {body.environment} deployment is already in progress. "
            "Wait for it to succeed or fail before starting another deployment."
        ) from exc
    await enqueue_run_project_deploy(str(d.id))
    return DeployResponse.model_validate(d)


@router.get(
    "/api/projects/{project}/deploys",
    response_model=list[DeployResponse],
)
async def list_deploys(
    project: UUID = Depends(require_active_billing_for_project),
    user: User = Depends(get_current_user),
    service: DeployService = Depends(get_deploy_service),
):
    deploys = await service.list_deploys(project)
    return [DeployResponse.model_validate(d) for d in deploys]


@router.post(
    "/api/deploys/{deploy}/rollback",
    response_model=DeployResponse,
    deprecated=True,
    summary="Deprecated: use POST /api/projects/{project_id}/deploys/{deploy}/rollback",
)
async def rollback_deploy(
    response: Response,
    deploy: UUID = Depends(require_active_billing_for_deploy),
    user: User = Depends(get_current_user),
    service: DeployService = Depends(get_deploy_service),
    db: AsyncSession = Depends(get_db),
):
    add_deprecation_header(response, "POST /api/projects/{project_id}/deploys/{deploy}/rollback")
    d = await service.rollback(deploy, user.id)
    await db.commit()
    await enqueue_run_project_deploy(str(d.id))
    return DeployResponse.model_validate(d)


def _domain_response(d: ProjectDomain) -> DomainResponse:
    return _domain_response_fn(d)


@router.get(
    "/api/projects/{project}/domains",
    response_model=list[DomainResponse],
)
async def list_domains(
    project: UUID = Depends(require_active_billing_for_project),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    domains = await DomainRepository(db).list_for_project(project)
    return [_domain_response(d) for d in domains]


@router.post(
    "/api/projects/{project}/domains", response_model=DomainResponse
)
async def add_domain(
    body: DomainCreate,
    project: UUID = Depends(require_active_billing_for_project),
    user: User = Depends(get_current_user),
    service: DeployService = Depends(get_deploy_service),
):
    d = await service.add_domain(project, body.domain, body.environment)
    return _domain_response(d)


@router.post(
    "/api/projects/{project}/domains/{domain}/verify",
    response_model=DomainResponse,
)
async def verify_domain(
    project: UUID = Depends(require_active_billing_for_project),
    domain: UUID = Path(...),
    user: User = Depends(get_current_user),
    service: DeployService = Depends(get_deploy_service),
):
    d, dns_ok = await service.verify_domain(domain)
    resp = _domain_response(d)
    if resp.dns_instructions:
        resp.dns_instructions.propagation_note = (
            "TXT record verified successfully." if dns_ok
            else "TXT record not found yet. DNS changes can take up to 24 hours to propagate."
        )
    return resp


@router.get(
    "/api/projects/{project}/domains/{domain}/dns-status",
)
async def check_domain_dns_status(
    project: UUID = Depends(require_active_billing_for_project),
    domain: UUID = Path(...),
    user: User = Depends(get_current_user),
    service: DeployService = Depends(get_deploy_service),
):
    """Poll DNS status without mutating anything. Use for live checking on the frontend."""
    return await service.check_dns_status(domain)


@router.post(
    "/api/projects/{project}/domains/{domain}/set-primary",
    response_model=DomainResponse,
)
async def set_primary_domain(
    project: UUID = Depends(require_active_billing_for_project),
    domain: UUID = Path(...),
    user: User = Depends(get_current_user),
    service: DeployService = Depends(get_deploy_service),
):
    d = await service.set_primary_domain(domain)
    return _domain_response(d)


@router.delete("/api/projects/{project}/domains/{domain}")
async def delete_domain(
    project: UUID = Depends(require_active_billing_for_project),
    domain: UUID = Path(...),
    user: User = Depends(get_current_user),
    service: DeployService = Depends(get_deploy_service),
):
    await service.delete_domain(domain)
    return {"message": "Domain deleted"}
