"""HTTP routes for user GitLab OAuth, project import, and mirror targets."""
from __future__ import annotations

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

import structlog
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_gitlab_user_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.gitlab_user_mirror.repository import (
    ProjectUserGitlabMirrorRepository,
    WorkspaceUserGitlabConnectionRepository,
)
from src.domain.gitlab_user_mirror.schemas import (
    GitlabConnectUrlResponse,
    GitlabProjectItem,
    GitlabUserConnectionResponse,
    ProjectGitlabMirrorResponse,
    ProjectGitlabMirrorUpsert,
)
from src.domain.gitlab_user_mirror.service import GitlabUserMirrorService
from src.domain.gitlab_user_mirror.verification import verify_gitlab_user_webhook_token
from src.domain.projects.repository import ProjectRepository
from src.domain.workspaces.facades import WorkspaceFacadeImpl
from src.domain.workspaces.repository import MembershipRepository, WorkspaceRepository

logger = structlog.get_logger()

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


def _service(db: AsyncSession) -> GitlabUserMirrorService:
    return GitlabUserMirrorService(
        WorkspaceUserGitlabConnectionRepository(db),
        ProjectUserGitlabMirrorRepository(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/gitlab-user/callback"


@webhook_router.post("/gitlab-user")
async def gitlab_user_webhook(
    request: Request,
    db: AsyncSession = Depends(get_db),
    x_gitlab_token: Optional[str] = Header(None),
    x_gitlab_event: Optional[str] = Header(None),
    x_gitlab_webhook_uuid: Optional[str] = Header(None),
    x_gitlab_event_uuid: Optional[str] = Header(None),
):
    if not verify_gitlab_user_webhook_token(x_gitlab_token):
        raise ForbiddenError("Invalid webhook token")
    payload = await request.json()
    from src.domain.gitlab_user_mirror import webhook_handlers

    result = await webhook_handlers.dispatch(db, x_gitlab_event or "", payload)
    await db.commit()
    return {
        "ok": True,
        "event": x_gitlab_event or "",
        "delivery": x_gitlab_webhook_uuid or x_gitlab_event_uuid or "",
        **result,
    }


@integrations_router.get("/gitlab-user/callback")
async def gitlab_user_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_gitlab_user_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}/gitlab-user/connect-url", response_model=GitlabConnectUrlResponse)
async def get_gitlab_user_connect_url(
    request: Request,
    ws: UUID = Depends(require_active_billing_for_workspace),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
    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_gitlab_user_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,
        callback_return_url=callback_return_url,
        redirect_uri=redirect_uri,
    )
    return GitlabConnectUrlResponse(
        connect_url=connect_url,
        state=state,
        callback_return_url=callback_return_url,
        redirect_uri=redirect_uri,
    )


@workspace_router.get(
    "/{ws}/gitlab-user/connections",
    response_model=list[GitlabUserConnectionResponse],
)
async def list_gitlab_user_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 WorkspaceUserGitlabConnectionRepository(db).list_for_workspace(ws)
    return [GitlabUserConnectionResponse.model_validate(r) for r in rows]


@workspace_router.delete("/{ws}/gitlab-user/connections/{connection_id}")
async def delete_gitlab_user_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 = WorkspaceUserGitlabConnectionRepository(db)
    row = await repo.get_by_id(connection_id)
    if not row or row.workspace_id != ws:
        raise NotFoundError("GitLab 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}/gitlab-user/connections/{connection_id}/projects",
    response_model=list[GitlabProjectItem],
)
async def list_gitlab_user_projects(
    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_projects(ws, connection_id)
    return [GitlabProjectItem.model_validate(x) for x in raw]


@project_mirror_router.put(
    "/{project_id}/gitlab-user-mirror",
    response_model=ProjectGitlabMirrorResponse,
)
async def upsert_project_gitlab_mirror(
    body: ProjectGitlabMirrorUpsert,
    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_gitlab_connection_id,
        target_project_full_path=body.target_project_full_path,
        target_default_branch=body.target_default_branch,
        enabled=body.enabled,
        check_permission=facade.check_permission,
    )
    await db.commit()
    return ProjectGitlabMirrorResponse.model_validate(row)


@project_mirror_router.delete("/{project_id}/gitlab-user-mirror")
async def delete_project_gitlab_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 GitLab mirror configured for this project")
    await db.commit()
    return {"ok": True}


@project_mirror_router.get(
    "/{project_id}/gitlab-user-mirror",
    response_model=ProjectGitlabMirrorResponse,
)
async def get_project_gitlab_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 ProjectUserGitlabMirrorRepository(db).get_by_project_id(project_id)
    if not row:
        raise NotFoundError("No GitLab mirror configured for this project")
    return ProjectGitlabMirrorResponse.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)
