import ipaddress
from enum import Enum
from typing import Any, NamedTuple

import structlog
from fastapi import APIRouter, HTTPException, Request, status

from skyvern.config import settings
from skyvern.forge import app
from skyvern.forge.sdk.services.local_org_auth_token_service import fingerprint_token, regenerate_local_api_key
from skyvern.forge.sdk.services.org_auth_service import resolve_org_from_api_key

router = APIRouter(prefix="/internal/auth", tags=["internal"])
LOG = structlog.get_logger()


class AuthStatus(str, Enum):
    missing_env = "missing_env"
    invalid_format = "invalid_format"
    invalid = "invalid"
    expired = "expired"
    not_found = "not_found"
    ok = "ok"


class DiagnosticsResult(NamedTuple):
    status: AuthStatus
    detail: str | None
    validation: Any | None
    token: str | None


def _is_local_request(request: Request) -> bool:
    host = request.client.host if request.client else None
    if not host:
        LOG.warning("No client host found in request", client=request.client)
        return False
    try:
        addr = ipaddress.ip_address(host)
    except ValueError:
        LOG.warning("Invalid IP address in request", host=host)
        return False
    # Check if request is from Docker host (gateway IP)
    # Docker typically uses 172.x.x.x or 192.168.x.x for bridge networks
    is_local = addr.is_loopback or addr.is_private
    LOG.info(
        "Checking if request is local",
        host=host,
        is_loopback=addr.is_loopback,
        is_private=addr.is_private,
        is_local=is_local,
    )
    return is_local


def _require_local_access(request: Request) -> None:
    if settings.ENV != "local":
        raise HTTPException(status.HTTP_403_FORBIDDEN, "Endpoint only available in local env")
    if not _is_local_request(request):
        raise HTTPException(status.HTTP_403_FORBIDDEN, "Endpoint requires localhost access")


async def _evaluate_local_api_key(token: str) -> DiagnosticsResult:
    token_candidate = token.strip()
    if not token_candidate or token_candidate == "YOUR_API_KEY":
        return DiagnosticsResult(status=AuthStatus.missing_env, detail=None, validation=None, token=None)

    try:
        validation = await resolve_org_from_api_key(token_candidate, app.DATABASE)
    except HTTPException as exc:
        if exc.status_code == status.HTTP_404_NOT_FOUND:
            return DiagnosticsResult(status=AuthStatus.not_found, detail=None, token=None, validation=None)

        detail_text = exc.detail if isinstance(exc.detail, str) else None
        if exc.status_code == status.HTTP_403_FORBIDDEN:
            status_value = AuthStatus.invalid
            if detail_text and "expired" in detail_text.lower():
                status_value = AuthStatus.expired
            elif detail_text and "validate" in detail_text.lower():
                status_value = AuthStatus.invalid_format
            return DiagnosticsResult(status=status_value, detail=detail_text, token=None, validation=None)

        LOG.error("Unexpected error while diagnosing API key", status_code=exc.status_code, detail=detail_text)
        raise
    except Exception:
        LOG.error("Unexpected exception while diagnosing API key", exc_info=True)
        raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, "Unable to diagnose API key")

    return DiagnosticsResult(status=AuthStatus.ok, detail=None, validation=validation, token=token_candidate)


def _emit_diagnostics(result: DiagnosticsResult) -> dict[str, object]:
    status_value = result.status.value

    if result.status is AuthStatus.ok and result.validation and result.token:
        fingerprint = fingerprint_token(result.token)
        LOG.info(
            "Local auth diagnostics",
            status=status_value,
            organization_id=result.validation.organization.organization_id,
            fingerprint=fingerprint,
            expires_at=result.validation.payload.exp,
        )
        return {
            "status": status_value,
            "organization_id": result.validation.organization.organization_id,
            "fingerprint": fingerprint,
            "expires_at": result.validation.payload.exp,
        }

    log_kwargs: dict[str, object] = {"status": status_value}
    if result.detail:
        log_kwargs["detail"] = result.detail

    LOG.warning("Local auth diagnostics", **log_kwargs)

    return {"status": status_value}


@router.post("/repair", include_in_schema=False)
async def repair_api_key(request: Request) -> dict[str, object]:
    _require_local_access(request)

    token, organization_id, backend_env_path, frontend_env_path = await regenerate_local_api_key()

    response: dict[str, object] = {
        "status": AuthStatus.ok.value,
        "organization_id": organization_id,
        "fingerprint": fingerprint_token(token),
        "api_key": token,
        "backend_env_path": backend_env_path,
    }

    if frontend_env_path:
        response["frontend_env_path"] = frontend_env_path

    return response


@router.get("/status", include_in_schema=False)
async def auth_status(request: Request) -> dict[str, object]:
    _require_local_access(request)
    token_candidate = request.headers.get("x-api-key") or ""
    result = await _evaluate_local_api_key(token_candidate)
    return _emit_diagnostics(result)
