"""HTTP routes: user GitHub App install, webhooks, project mirror targets."""
from __future__ import annotations

import json
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 JSONResponse, RedirectResponse
from sqlalchemy.ext.asyncio import AsyncSession

from src.core.app_url import resolve_github_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, ValidationError
from src.domain.auth.models import User
from src.domain.auth.router import get_current_user
from src.domain.github_user_mirror.repository import (
    ProjectUserMirrorRepository,
    WorkspaceUserGithubInstallationRepository,
)
from src.domain.github_user_mirror.schemas import (
    GithubUserInstallationCreate,
    GithubUserInstallationResponse,
    GithubUserRepoItem,
    InstallUrlResponse,
    ProjectMirrorResponse,
    ProjectMirrorUpsert,
)
from src.domain.github_user_mirror.service import (
    GithubUserMirrorService,
    decode_github_install_state_payload,
    parse_github_installation_id,
)
from src.domain.github_user_mirror.verification import verify_github_user_app_signature
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 (no auth; HMAC verified) ─────────────────────────────────────────
webhook_router = APIRouter(prefix="/api/webhooks", tags=["github-user-webhooks"])


@webhook_router.post("/github-user")
async def github_user_app_webhook(
    request: Request,
    db: AsyncSession = Depends(get_db),
    x_hub_signature_256: Optional[str] = Header(None),
    x_github_event: Optional[str] = Header(None),
    x_github_delivery: Optional[str] = Header(None),
):
    body = await request.body()
    if not verify_github_user_app_signature(body, x_hub_signature_256):
        logger.warning(
            "github_user_app_webhook_signature_rejected",
            has_sig=bool(x_hub_signature_256),
        )
        raise ForbiddenError("Invalid webhook signature")

    payload = json.loads(body)
    event = x_github_event or ""
    delivery = x_github_delivery or ""

    from src.domain.github_user_mirror import webhook_handlers

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


# ── Post-install: GitHub → this backend only → correct frontend ─────────────
# Configure the GitHub App "Setup URL" / post-install redirect to ONE backend URL
# (e.g. …/api/integrations/github-user/callback). Do not list customer SPA domains in
# GitHub; multi-tenant return targets are carried in ``state`` (JWT claim ``ru``) from
# GET …/install-url, with env-based fallback when ``state`` is missing.
integrations_router = APIRouter(prefix="/api/integrations", tags=["github-user-mirror"])


async def _github_user_post_install_redirect(
    request: Request,
    installation_id: int,
    setup_action: str,
    state: Optional[str],
    db: Optional[AsyncSession] = None,
):
    """Redirect browser to the right SPA after GitHub install (see module doc above)."""
    base: Optional[str] = None
    state_payload: Optional[dict] = None
    if state:
        try:
            state_payload = decode_github_install_state_payload(state)
            ru = state_payload.get("return_url")
            if ru and ru.strip():
                base = ru.strip().rstrip("/")
        except ValidationError:
            logger.warning("github_user_post_install_invalid_or_expired_state")

    if not base:
        try:
            base = resolve_github_user_mirror_return_url(request).rstrip("/")
        except ValidationError as e:
            return JSONResponse(status_code=400, content={"detail": str(e)})

    if db is not None and state_payload is not None:
        try:
            svc = _mirror_service(db)
            await svc.confirm_installation(
                workspace_id=state_payload["workspace_id"],
                user_id=state_payload["user_id"],
                installation_id=installation_id,
                state=state,
            )
            await db.commit()
        except Exception as e:
            await db.rollback()
            logger.warning(
                "github_user_post_install_autolink_failed",
                installation_id=installation_id,
                error=str(e),
            )

    query = {"installation_id": installation_id, "setup_action": setup_action}
    if state:
        try:
            redirect_state_payload = state_payload or decode_github_install_state_payload(state)
            import_project_id = redirect_state_payload.get("import_project_id")
            if import_project_id:
                query["project_id"] = str(import_project_id)
        except ValidationError:
            pass
    q = urllib.parse.urlencode(query)
    sep = "&" if "?" in base else "?"
    return RedirectResponse(f"{base}{sep}{q}", status_code=302)


@integrations_router.get("/github-user/callback")
async def github_user_install_callback(
    request: Request,
    db: AsyncSession = Depends(get_db),
    installation_id: int = Query(..., gt=0),
    setup_action: str = Query("install"),
    state: Optional[str] = Query(
        None,
        description="JWT from install-url; embeds the tenant frontend return URL (``ru``).",
    ),
):
    """Forward GitHub post-install redirects to the correct app origin."""
    return await _github_user_post_install_redirect(
        request, installation_id, setup_action, state, db
    )


@integrations_router.get("/github-user/setup")
async def github_user_setup_redirect(
    request: Request,
    db: AsyncSession = Depends(get_db),
    installation_id: int = Query(..., gt=0),
    setup_action: str = Query("install"),
    state: Optional[str] = Query(
        None,
        description="JWT from install flow; embeds the frontend return URL after install.",
    ),
):
    """Backward-compatible alias; prefer ``/github-user/callback`` in GitHub App settings."""
    return await _github_user_post_install_redirect(
        request, installation_id, setup_action, state, db
    )


# ── Workspace-scoped (authenticated) ───────────────────────────────────────
workspace_router = APIRouter(prefix="/api/workspaces", tags=["github-user-mirror"])


def _mirror_service(db: AsyncSession) -> GithubUserMirrorService:
    return GithubUserMirrorService(
        WorkspaceUserGithubInstallationRepository(db),
        ProjectUserMirrorRepository(db),
        ProjectRepository(db),
    )


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


@workspace_router.get("/{ws}/github-user/install-url", response_model=InstallUrlResponse)
async def get_github_user_install_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,
        description=(
            "Optional frontend origin or full callback URL; must match APP_URL_WHITELIST "
            "when set."
        ),
    ),
):
    facade = _facade(db)
    if not await facade.check_permission(ws, user.id, "repos.manage"):
        raise ForbiddenError()
    svc = _mirror_service(db)
    callback_return_url = resolve_github_user_mirror_return_url(
        request, redirect_base=redirect_base
    )
    url, state = svc.build_install_url(ws, user.id, callback_return_url)
    return InstallUrlResponse(
        install_url=url, state=state, callback_return_url=callback_return_url
    )


@workspace_router.post(
    "/{ws}/github-user/installations",
    response_model=GithubUserInstallationResponse,
)
async def confirm_github_user_installation(
    body: GithubUserInstallationCreate,
    ws: UUID = Depends(require_active_billing_for_workspace),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
    state: Optional[str] = Query(
        None,
        description="Same state returned from install-url (binds workspace + user).",
    ),
):
    facade = _facade(db)
    if not await facade.check_permission(ws, user.id, "repos.manage"):
        raise ForbiddenError()
    svc = _mirror_service(db)
    installation_id = body.installation_id
    if installation_id is None and body.installation_url:
        installation_id = parse_github_installation_id(body.installation_url)
    if installation_id is None:
        raise ValidationError("installation_id or installation_url is required.")
    row = await svc.confirm_installation(
        workspace_id=ws,
        user_id=user.id,
        installation_id=installation_id,
        state=state or body.state,
    )
    await db.commit()
    return GithubUserInstallationResponse.model_validate(row)


@workspace_router.get(
    "/{ws}/github-user/installations",
    response_model=list[GithubUserInstallationResponse],
)
async def list_github_user_installations(
    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 = WorkspaceUserGithubInstallationRepository(db)
    rows = await repo.list_for_workspace(ws)
    return [GithubUserInstallationResponse.model_validate(r) for r in rows]


@workspace_router.delete("/{ws}/github-user/installations/{installation_row_id}")
async def delete_github_user_installation(
    installation_row_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 = WorkspaceUserGithubInstallationRepository(db)
    row = await repo.get_by_id(installation_row_id)
    if not row or row.workspace_id != ws:
        raise NotFoundError("Installation not found")
    await repo.delete(installation_row_id)
    await db.commit()
    return {"ok": True}


@workspace_router.get(
    "/{ws}/github-user/installations/{installation_row_id}/repositories",
    response_model=list[GithubUserRepoItem],
)
async def list_github_user_installation_repos(
    installation_row_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()
    svc = _mirror_service(db)
    raw = await svc.list_installation_repos(ws, installation_row_id)
    return [GithubUserRepoItem.model_validate(x) for x in raw]


# ── Project mirror (per-project target repo) ─────────────────────────────────
project_mirror_router = APIRouter(prefix="/api/projects", tags=["github-user-mirror"])


@project_mirror_router.put(
    "/{project_id}/github-user-mirror",
    response_model=ProjectMirrorResponse,
)
async def upsert_project_github_mirror(
    body: ProjectMirrorUpsert,
    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)
    svc = _mirror_service(db)

    try:
        row = await svc.upsert_project_mirror(
            project_id=project_id,
            workspace_id=proj.workspace_id,
            user_id=user.id,
            installation_row_id=body.workspace_user_github_installation_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,
        )
    except ForbiddenError:
        raise
    await db.commit()
    return ProjectMirrorResponse.model_validate(row)


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