from __future__ import annotations

from datetime import datetime, timedelta
import json
import logging
import re
import secrets
from typing import Any, Dict, Optional
from urllib.parse import urlencode

import httpx
import jwt
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.responses import HTMLResponse
from sqlalchemy.ext.asyncio import AsyncSession

from ... import settings
from ...auth_core import OrgContext, require_org_context
from ...auth_security import require_account
from ...db import get_session
from ...models import IntegrationStatus
from ...services.apollo_oauth import (
    APOLLO_TOKEN_URL,
    get_apollo_integration,
    get_or_create_apollo_integration,
    refresh_apollo_tokens,
    store_apollo_tokens,
)
from ...services.secret_storage import get_saas_secret_backend
from ...services.usage_gateway import UsageGateway
from ...api.progress import get_redis

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/oauth/apollo", tags=["Apollo OAuth"])

NONCE_TTL_SECONDS = 600
CLI_SESSION_TTL_SECONDS = 300


def _state_secret() -> str:
    return settings.ENCRYPTION_KEY or settings.JWT_SECRET_KEY or "dev-secret"


def _encode_state(nonce: str, flow: str) -> str:
    payload = {
        "nonce": nonce,
        "flow": flow,
        "exp": int((datetime.utcnow() + timedelta(seconds=NONCE_TTL_SECONDS)).timestamp()),
    }
    return jwt.encode(payload, _state_secret(), algorithm="HS256")


def _decode_state(state_token: str) -> Dict[str, Any]:
    return jwt.decode(state_token, _state_secret(), algorithms=["HS256"])


async def _redis_get_json(redis, key: str) -> Optional[Dict[str, Any]]:
    raw = await redis.get(key)
    if not raw:
        return None
    if isinstance(raw, bytes):
        raw = raw.decode("utf-8", errors="ignore")
    try:
        return json.loads(raw)
    except json.JSONDecodeError:
        return None


async def _set_cli_session_status(
    redis,
    session_id: Optional[str],
    status: str,
    message: Optional[str] = None,
    workspace_id: Optional[str] = None,
) -> None:
    if not session_id:
        return
    payload: Dict[str, Any] = {"status": status}
    if message:
        payload["message"] = message
    if workspace_id:
        payload["workspace_id"] = workspace_id
    await redis.setex(
        f"apollo_cli_session:{session_id}",
        CLI_SESSION_TTL_SECONDS,
        json.dumps(payload),
    )


def _html_page(title: str, message: str, is_error: bool = False) -> HTMLResponse:
    status_class = "error" if is_error else "success"
    html = f"""
    <html>
      <head>
        <title>{title}</title>
        <style>
          body {{ font-family: Arial, sans-serif; background: #f6f7fb; color: #111; }}
          .card {{ max-width: 520px; margin: 10vh auto; background: #fff; padding: 32px;
            border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.08); }}
          .{status_class} {{ color: {"#b00020" if is_error else "#0f8a3b"}; font-weight: 700; }}
          .msg {{ margin-top: 12px; line-height: 1.5; }}
        </style>
      </head>
      <body>
        <div class="card">
          <div class="{status_class}">{title}</div>
          <div class="msg">{message}</div>
          <div class="msg">You can close this window.</div>
        </div>
        <script>
          setTimeout(function() {{
            try {{ window.close(); }} catch (e) {{}}
          }}, 1500);
        </script>
      </body>
    </html>
    """
    return HTMLResponse(content=html, status_code=200)


def _error_html(message: str) -> HTMLResponse:
    return _html_page("Apollo OAuth Failed", message, is_error=True)


def _success_html(message: str) -> HTMLResponse:
    return _html_page("Apollo Connected", message, is_error=False)


def _build_auth_url(redirect_uri: str, state_token: str) -> str:
    params = {
        "response_type": "code",
        "client_id": settings.APOLLO_CLIENT_ID,
        "redirect_uri": redirect_uri,
        "state": state_token,
    }
    auth_url = f"https://app.apollo.io/#/oauth/authorize?{urlencode(params)}"
    if "/#/" not in auth_url:
        logger.warning("Apollo auth URL missing '/#/' route: %s", auth_url)
    redacted = re.sub(r"state=[^&]+", "state=REDACTED", auth_url)
    logger.info("Apollo auth URL built: %s", redacted)
    return auth_url


async def _exchange_code_for_tokens(code: str, redirect_uri: str) -> Dict[str, Any]:
    async with httpx.AsyncClient(timeout=30.0) as client:
        resp = await client.post(
            APOLLO_TOKEN_URL,
            data={
                "grant_type": "authorization_code",
                "code": code,
                "client_id": settings.APOLLO_CLIENT_ID,
                "client_secret": settings.APOLLO_CLIENT_SECRET,
                "redirect_uri": redirect_uri,
            },
        )
    if not resp.is_success:
        snippet = (resp.text or "")[:400]
        detail = f"Token exchange failed ({resp.status_code})."
        if resp.status_code == 404:
            detail += " Check Apollo OAuth app settings and redirect URL."
        raise RuntimeError(f"{detail} Response: {snippet}")
    try:
        data = resp.json()
    except ValueError:
        raise RuntimeError("Token exchange failed: invalid JSON response")
    if not data.get("access_token") or not data.get("refresh_token"):
        raise RuntimeError("Token exchange response missing access/refresh token")
    return data


@router.get("/authorize")
async def start_apollo_connect(
    request: Request,
    org: OrgContext = Depends(require_org_context),
    db: AsyncSession = Depends(get_session),
    redis=Depends(get_redis),
):
    gateway = UsageGateway(db)
    capabilities = await gateway.get_capabilities(org.account_id)
    if not capabilities.get("byo", {}).get("enabled", False):
        raise HTTPException(403, {"error": "TIER_REQUIRED"})

    if not settings.APOLLO_CLIENT_ID or not settings.APOLLO_CLIENT_SECRET:
        raise HTTPException(503, "Apollo OAuth not configured")

    nonce = secrets.token_urlsafe(32)
    await redis.setex(
        f"apollo_oauth_nonce:{nonce}",
        NONCE_TTL_SECONDS,
        json.dumps(
            {
                "account_id": org.account_id,
                "user_id": org.user_id,
                "email": org.email,
            }
        ),
    )

    state_token = _encode_state(nonce=nonce, flow="sheets")
    auth_url = _build_auth_url(settings.APOLLO_REDIRECT_URL, state_token)
    return {"auth_url": auth_url}


@router.get("/callback")
async def oauth_callback(
    request: Request,
    code: Optional[str] = Query(None),
    state: str = Query(...),
    error: Optional[str] = Query(None),
    error_description: Optional[str] = Query(None),
    db: AsyncSession = Depends(get_session),
    redis=Depends(get_redis),
):
    try:
        state_data = _decode_state(state)
        flow = state_data.get("flow")
        if flow not in {"sheets", "cli"}:
            return _error_html("Invalid OAuth flow.")
        nonce = state_data["nonce"]
    except jwt.ExpiredSignatureError:
        return _error_html("State expired. Please try again.")
    except jwt.InvalidTokenError:
        return _error_html("Invalid state.")

    nonce_key = f"apollo_oauth_nonce:{nonce}"
    nonce_data = await _redis_get_json(redis, nonce_key)
    if not nonce_data:
        return _error_html("Invalid or expired nonce.")

    await redis.delete(nonce_key)
    account_id = nonce_data.get("account_id")
    session_id = nonce_data.get("cli_session_id") if flow == "cli" else None
    user_email = nonce_data.get("email") or ("cli" if flow == "cli" else "sheets")

    if flow == "cli":
        if not session_id or not account_id:
            message = "Missing CLI session or workspace context."
            await _set_cli_session_status(redis, session_id, "error", message=message)
            return _error_html(message)
    else:
        if not account_id:
            return _error_html("Missing workspace context.")

    if error or not code:
        if error:
            description = error_description or error
            if "access_denied" in error or "403" in str(description or ""):
                message = (
                    "Your Apollo account doesn't have permission to authorize third-party apps. "
                    "Please contact your Apollo administrator."
                )
            else:
                message = f"Authorization failed: {description}"
        else:
            message = "Missing authorization code."
        if flow == "cli":
            await _set_cli_session_status(redis, session_id, "error", message=message)
        return _error_html(message)

    try:
        tokens = await _exchange_code_for_tokens(
            code=code,
            redirect_uri=settings.APOLLO_REDIRECT_URL,
        )
    except Exception as exc:
        logger.error("Apollo token exchange failed: %s", exc)
        if flow == "cli":
            await _set_cli_session_status(redis, session_id, "error", message=str(exc))
        return _error_html(str(exc))

    await store_apollo_tokens(
        db=db,
        account_id=str(account_id),
        tokens=tokens,
        created_by=user_email,
    )
    await db.commit()

    if flow == "cli":
        await _set_cli_session_status(
            redis,
            session_id,
            "complete",
            workspace_id=str(account_id),
        )
        return _success_html("CLI authorization complete. You can close this window.")
    return _success_html("Apollo connected successfully!")


@router.get("/status")
async def apollo_status(
    org: OrgContext = Depends(require_org_context),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    integration = await get_apollo_integration(db, org.account_id)
    if not integration:
        return {"status": "disconnected", "connected": False}
    connected = integration.status == IntegrationStatus.CONNECTED
    config = integration.config or {}
    return {
        "status": integration.status.value if hasattr(integration.status, "value") else integration.status,
        "connected": connected,
        "connected_at": config.get("connected_at"),
        "expires_at": config.get("expires_at"),
    }


@router.delete("")
async def disconnect_apollo(
    account_id: str = Depends(require_account),
    db: AsyncSession = Depends(get_session),
    redis=Depends(get_redis),
):
    secret_backend = get_saas_secret_backend()
    await secret_backend.delete_secret(db, "apollo", "access_token", account_id)
    await secret_backend.delete_secret(db, "apollo", "refresh_token", account_id)

    integration = await get_or_create_apollo_integration(db, account_id)
    integration.status = IntegrationStatus.DISCONNECTED
    config = dict(integration.config or {})
    config["disconnected_at"] = datetime.utcnow().isoformat()
    integration.config = config
    integration.updated_at = datetime.utcnow()

    pattern = f"apollo_cli_session:*:{account_id}"
    async for key in redis.scan_iter(pattern):
        await redis.delete(key)

    await db.commit()
    return {"ok": True, "message": "Apollo disconnected"}


@router.get("/cli/authorize")
async def cli_authorize(
    session_id: str = Query(...),
    workspace_id: str = Query(..., description="Workspace/org ID for Apollo connection"),
    db: AsyncSession = Depends(get_session),
    redis=Depends(get_redis),
):
    gateway = UsageGateway(db)
    capabilities = await gateway.get_capabilities(workspace_id)
    if not capabilities.get("byo", {}).get("enabled", False):
        raise HTTPException(403, {"error": "TIER_REQUIRED"})

    if not settings.APOLLO_CLIENT_ID or not settings.APOLLO_CLIENT_SECRET:
        raise HTTPException(503, "Apollo OAuth not configured")

    nonce = secrets.token_urlsafe(32)
    await redis.setex(
        f"apollo_oauth_nonce:{nonce}",
        NONCE_TTL_SECONDS,
        json.dumps(
            {
                "cli_session_id": session_id,
                "account_id": workspace_id,
            }
        ),
    )

    state_token = _encode_state(nonce=nonce, flow="cli")
    auth_url = _build_auth_url(settings.APOLLO_REDIRECT_URL, state_token)
    return {"auth_url": auth_url}


@router.get("/cli/poll")
async def cli_poll(
    session_id: str = Query(...),
    redis=Depends(get_redis),
):
    data = await _redis_get_json(redis, f"apollo_cli_session:{session_id}")
    if not data:
        return {"status": "pending"}
    return data


@router.get("/cli/token")
async def cli_get_token(
    account_id: str = Depends(require_account),
    db: AsyncSession = Depends(get_session),
):
    integration = await get_apollo_integration(db, account_id)
    if not integration or integration.status != IntegrationStatus.CONNECTED:
        raise HTTPException(401, "Apollo not connected")

    expires_at = None
    config = integration.config or {}
    expires_raw = config.get("expires_at")
    if expires_raw:
        try:
            expires_at = datetime.fromisoformat(str(expires_raw))
        except ValueError:
            expires_at = None

    if not expires_at or expires_at < datetime.utcnow() + timedelta(seconds=60):
        refreshed = await refresh_apollo_tokens(db, account_id)
        if not refreshed:
            raise HTTPException(401, "Token refresh failed. Please reconnect.")
        integration = await get_apollo_integration(db, account_id)
        if integration:
            config = integration.config or {}

    secret_backend = get_saas_secret_backend()
    access_token = await secret_backend.get_secret(
        db=db,
        integration="apollo",
        key="access_token",
        workspace_id=account_id,
    )
    if not access_token:
        raise HTTPException(401, "Apollo access token missing. Please reconnect.")

    return {
        "access_token": access_token.value,
        "expires_at": config.get("expires_at"),
    }
