"""HTTP routes for Azure DevOps OAuth/PAT connections and project mirror targets."""
from __future__ import annotations

import urllib.parse
from typing import Optional
from uuid import UUID

from fastapi import APIRouter, Depends, Header, Path, Query, Request
from fastapi.responses import RedirectResponse
from sqlalchemy.ext.asyncio import AsyncSession

from src.config import get_settings
from src.core.app_url import resolve_azure_devops_mirror_return_url
from src.core.database import get_db
from src.core.dependencies import (
    require_active_billing_for_project_id,
    require_active_billing_for_workspace,
)
from src.core.exceptions import ForbiddenError, NotFoundError
from src.domain.auth.models import User
from src.domain.auth.router import get_current_user
from src.domain.azure_user_mirror.repository import (
    ProjectUserAzureMirrorRepository,
    WorkspaceUserAzureConnectionRepository,
)
from src.domain.azure_user_mirror.schemas import (
    AzureConnectionCreate,
    AzureConnectUrlResponse,
    AzureRepoItem,
    AzureUserConnectionResponse,
    ProjectAzureMirrorResponse,
    ProjectAzureMirrorUpsert,
)
from src.domain.azure_user_mirror.service import AzureUserMirrorService
from src.domain.azure_user_mirror.verification import verify_azure_devops_webhook_token
from src.domain.projects.repository import ProjectRepository
from src.domain.workspaces.facades import WorkspaceFacadeImpl
from src.domain.workspaces.repository import MembershipRepository, WorkspaceRepository

webhook_router = APIRouter(prefix="/api/webhooks", tags=["azure-devops-webhooks"])
integrations_router = APIRouter(prefix="/api/integrations", tags=["azure-user-mirror"])
workspace_router = APIRouter(prefix="/api/workspaces", tags=["azure-user-mirror"])
project_mirror_router = APIRouter(prefix="/api/projects", tags=["azure-user-mirror"])


def _service(db: AsyncSession) -> AzureUserMirrorService:
    return AzureUserMirrorService(
        WorkspaceUserAzureConnectionRepository(db),
        ProjectUserAzureMirrorRepository(db),
        ProjectRepository(db),
    )


def _facade(db: AsyncSession) -> WorkspaceFacadeImpl:
    return WorkspaceFacadeImpl(WorkspaceRepository(db), MembershipRepository(db))


def _api_callback_url(request: Request) -> str:
    settings = get_settings()
    api_base = (settings.api_url or str(request.base_url).rstrip("/")).rstrip("/")
    return f"{api_base}/api/integrations/azure-devops/callback"


@webhook_router.post("/azure-devops")
async def azure_devops_webhook(
    request: Request,
    db: AsyncSession = Depends(get_db),
    x_azure_token: Optional[str] = Header(None),
):
    if not verify_azure_devops_webhook_token(x_azure_token):
        raise ForbiddenError("Invalid webhook token")
    payload = await request.json()
    event_type = payload.get("eventType") or ""
    from src.domain.azure_user_mirror import webhook_handlers
    result = await webhook_handlers.dispatch(db, event_type, payload)
    await db.commit()
    return {"ok": True, "event_type": event_type, **result}


@integrations_router.get("/azure-devops/callback")
async def azure_devops_oauth_callback(
    request: Request,
    code: str = Query(...),
    state: str = Query(...),
    db: AsyncSession = Depends(get_db),
):
    row, state_payload = await _service(db).exchange_callback(
        code=code,
        state=state,
        redirect_uri=_api_callback_url(request),
    )
    await db.commit()
    base = (
        state_payload.get("return_url") or resolve_azure_devops_mirror_return_url(request)
    ).rstrip("/")
    query = {"connection_id": str(row.id)}
    if state_payload.get("import_project_id"):
        query["project_id"] = str(state_payload["import_project_id"])
    sep = "&" if "?" in base else "?"
    return RedirectResponse(f"{base}{sep}{urllib.parse.urlencode(query)}", status_code=302)


@workspace_router.get(
    "/{ws}/azure-devops/connect-url",
    response_model=AzureConnectUrlResponse,
)
async def get_azure_devops_connect_url(
    request: Request,
    ws: UUID = Depends(require_active_billing_for_workspace),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
    azure_org_url: str = Query(
        ...,
        description="Azure DevOps org URL, e.g. https://dev.azure.com/myorg",
    ),
    redirect_base: Optional[str] = Query(None),
):
    facade = _facade(db)
    if not await facade.check_permission(ws, user.id, "repos.manage"):
        raise ForbiddenError()
    callback_return_url = resolve_azure_devops_mirror_return_url(
        request,
        redirect_base=redirect_base,
    )
    redirect_uri = _api_callback_url(request)
    connect_url, state = _service(db).build_connect_url(
        workspace_id=ws,
        user_id=user.id,
        azure_org_url=azure_org_url,
        callback_return_url=callback_return_url,
        redirect_uri=redirect_uri,
    )
    return AzureConnectUrlResponse(
        connect_url=connect_url,
        state=state,
        callback_return_url=callback_return_url,
        redirect_uri=redirect_uri,
    )


@workspace_router.post(
    "/{ws}/azure-devops/connections",
    response_model=AzureUserConnectionResponse,
)
async def create_azure_connection(
    body: AzureConnectionCreate,
    ws: UUID = Depends(require_active_billing_for_workspace),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    facade = _facade(db)
    if not await facade.check_permission(ws, user.id, "repos.manage"):
        raise ForbiddenError()
    row = await _service(db).create_connection(
        workspace_id=ws,
        user_id=user.id,
        azure_org_url=body.azure_org_url,
        username=body.username,
        display_name=body.display_name,
        pat=body.pat,
    )
    await db.commit()
    return AzureUserConnectionResponse.model_validate(row)


@workspace_router.get(
    "/{ws}/azure-devops/connections",
    response_model=list[AzureUserConnectionResponse],
)
async def list_azure_connections(
    ws: UUID = Depends(require_active_billing_for_workspace),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    facade = _facade(db)
    if not await facade.check_permission(ws, user.id, "repos.manage"):
        raise ForbiddenError()
    rows = await WorkspaceUserAzureConnectionRepository(db).list_for_workspace(ws)
    return [AzureUserConnectionResponse.model_validate(r) for r in rows]


@workspace_router.delete("/{ws}/azure-devops/connections/{connection_id}")
async def delete_azure_connection(
    connection_id: UUID = Path(...),
    ws: UUID = Depends(require_active_billing_for_workspace),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    facade = _facade(db)
    if not await facade.check_permission(ws, user.id, "repos.manage"):
        raise ForbiddenError()
    repo = WorkspaceUserAzureConnectionRepository(db)
    row = await repo.get_by_id(connection_id)
    if not row or row.workspace_id != ws:
        raise NotFoundError("Azure DevOps connection not found")
    await _service(db).cleanup_webhooks_for_connection(connection_id)
    await repo.delete(connection_id)
    await db.commit()
    return {"ok": True}


@workspace_router.get(
    "/{ws}/azure-devops/connections/{connection_id}/repositories",
    response_model=list[AzureRepoItem],
)
async def list_azure_repositories(
    connection_id: UUID = Path(...),
    ws: UUID = Depends(require_active_billing_for_workspace),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    facade = _facade(db)
    if not await facade.check_permission(ws, user.id, "repos.manage"):
        raise ForbiddenError()
    raw = await _service(db).list_connection_repositories(ws, connection_id)
    return [AzureRepoItem.model_validate(x) for x in raw]


@project_mirror_router.put(
    "/{project_id}/azure-devops-mirror",
    response_model=ProjectAzureMirrorResponse,
)
async def upsert_project_azure_mirror(
    body: ProjectAzureMirrorUpsert,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    proj = await ProjectRepository(db).get_by_id(project_id)
    if not proj:
        raise NotFoundError("Project not found")
    facade = _facade(db)
    row = await _service(db).upsert_project_mirror(
        project_id=project_id,
        workspace_id=proj.workspace_id,
        user_id=user.id,
        connection_row_id=body.workspace_user_azure_connection_id,
        target_repo_full_name=body.target_repo_full_name,
        target_default_branch=body.target_default_branch,
        enabled=body.enabled,
        check_permission=facade.check_permission,
    )
    await db.commit()
    return ProjectAzureMirrorResponse.model_validate(row)


@project_mirror_router.delete("/{project_id}/azure-devops-mirror")
async def delete_project_azure_mirror(
    project_id: UUID = Depends(require_active_billing_for_project_id),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    proj = await ProjectRepository(db).get_by_id(project_id)
    if not proj:
        raise NotFoundError("Project not found")
    facade = _facade(db)
    deleted = await _service(db).delete_project_mirror(
        project_id=project_id,
        workspace_id=proj.workspace_id,
        user_id=user.id,
        check_permission=facade.check_permission,
    )
    if not deleted:
        raise NotFoundError("No Azure DevOps mirror configured for this project")
    await db.commit()
    return {"ok": True}


@project_mirror_router.get(
    "/{project_id}/azure-devops-mirror",
    response_model=ProjectAzureMirrorResponse,
)
async def get_project_azure_mirror(
    project_id: UUID = Depends(require_active_billing_for_project_id),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    proj = await ProjectRepository(db).get_by_id(project_id)
    if not proj:
        raise NotFoundError("Project not found")
    facade = _facade(db)
    if not await facade.check_permission(proj.workspace_id, user.id, "projects.read"):
        raise ForbiddenError()
    row = await ProjectUserAzureMirrorRepository(db).get_by_project_id(project_id)
    if not row:
        raise NotFoundError("No Azure DevOps mirror configured for this project")
    return ProjectAzureMirrorResponse.model_validate(row)


router = APIRouter()
router.include_router(webhook_router)
router.include_router(integrations_router)
router.include_router(workspace_router)
router.include_router(project_mirror_router)
