"""Integration diagnostics — checks all external service configurations at runtime.

Runs on API + worker startup to surface misconfiguration early (logged as warnings,
never fatal). Also exposed via GET /health/integrations for on-demand inspection.

Each check returns a CheckResult: ok=True/False, name, detail (human-readable),
and optional fix_hint so the operator knows exactly what to change.
"""
from __future__ import annotations

import asyncio
from dataclasses import dataclass, field
from typing import Optional

import structlog

logger = structlog.get_logger()


@dataclass
class CheckResult:
    name: str
    ok: bool
    detail: str
    fix_hint: str = ""


@dataclass
class DiagnosticsReport:
    checks: list[CheckResult] = field(default_factory=list)

    @property
    def all_ok(self) -> bool:
        return all(c.ok for c in self.checks)

    @property
    def failures(self) -> list[CheckResult]:
        return [c for c in self.checks if not c.ok]

    def to_dict(self) -> dict:
        return {
            "all_ok": self.all_ok,
            "checks": [
                {
                    "name": c.name,
                    "ok": c.ok,
                    "detail": c.detail,
                    **({"fix_hint": c.fix_hint} if c.fix_hint else {}),
                }
                for c in self.checks
            ],
        }


# ---------------------------------------------------------------------------
# Individual checks
# ---------------------------------------------------------------------------

async def check_github_app() -> list[CheckResult]:
    """Verify GitHub App JWT works, permissions are granted, and events are subscribed."""
    import httpx

    results: list[CheckResult] = []

    def _sync_jwt_and_policy() -> tuple[str, dict[str, str], list[str]]:
        """PEM load + JWT + importing policy runs in a worker thread — avoids blocking the API event loop."""
        from src.domain.github_webhooks.permissions import REQUIRED_PERMISSIONS, SUBSCRIBED_EVENTS
        from src.integrations.github_client import GitHubAppClient

        client = GitHubAppClient()
        jwt = client._generate_app_jwt()
        return jwt, dict(REQUIRED_PERMISSIONS), list(SUBSCRIBED_EVENTS)

    try:
        jwt, required_perms, subscribed_events = await asyncio.to_thread(_sync_jwt_and_policy)
    except Exception as e:
        results.append(CheckResult(
            name="github_app_jwt",
            ok=False,
            detail=str(e),
            fix_hint="Set GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY (or GITHUB_APP_PRIVATE_KEY_FILE).",
        ))
        return results

    # ── GET /app — fetch live permissions + subscribed events ────────────────
    try:
        async with httpx.AsyncClient(timeout=10) as http:
            resp = await http.get(
                "https://api.github.com/app",
                headers={
                    "Authorization": f"Bearer {jwt}",
                    "Accept": "application/vnd.github+json",
                },
            )
            resp.raise_for_status()
            app_data = resp.json()
    except Exception as e:
        results.append(CheckResult(
            name="github_app_reachable",
            ok=False,
            detail=f"GET /app failed: {e}",
            fix_hint="Check GITHUB_APP_ID and that the private key belongs to that app.",
        ))
        return results

    results.append(CheckResult(
        name="github_app_reachable",
        ok=True,
        detail=f"App '{app_data.get('name')}' (id={app_data.get('id')}) authenticated OK.",
    ))

    # ── Permissions ───────────────────────────────────────────────────────────
    live_perms: dict = app_data.get("permissions", {})
    _LEVEL = {"read": 1, "write": 2, "admin": 3}

    for perm_name, required_level in required_perms.items():
        live_level = live_perms.get(perm_name, "none")
        ok = _LEVEL.get(live_level, 0) >= _LEVEL.get(required_level, 0)
        results.append(CheckResult(
            name=f"github_perm_{perm_name}",
            ok=ok,
            detail=(
                f"'{perm_name}' is '{live_level}' (need '{required_level}')."
                if not ok else
                f"'{perm_name}' = '{live_level}' ✓"
            ),
            fix_hint=(
                f"GitHub App settings → Permissions & events → Repository permissions "
                f"→ set '{perm_name}' to '{required_level}' or higher."
            ) if not ok else "",
        ))

    # ── Subscribed webhook events ─────────────────────────────────────────────
    live_events: list[str] = app_data.get("events", [])
    for event in subscribed_events:
        ok = event in live_events
        results.append(CheckResult(
            name=f"github_event_{event}",
            ok=ok,
            detail=f"'{event}' subscribed ✓" if ok else f"'{event}' NOT subscribed.",
            fix_hint=(
                f"GitHub App settings → Permissions & events → Subscribe to events → tick '{event}'."
            ) if not ok else "",
        ))

    return results


async def check_cloudflare() -> list[CheckResult]:
    """Verify Cloudflare API token can access the configured account's Pages projects."""
    from src.config import get_settings
    import httpx

    settings = get_settings()
    results: list[CheckResult] = []

    token = (settings.cloudflare_api_token or "").strip()
    account_id = (settings.cloudflare_account_id or "").strip()

    if not token or not account_id:
        results.append(CheckResult(
            name="cloudflare_config",
            ok=False,
            detail="CLOUDFLARE_API_TOKEN or CLOUDFLARE_ACCOUNT_ID is not set.",
            fix_hint="Set both CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID in your environment.",
        ))
        return results

    try:
        async with httpx.AsyncClient(timeout=10) as http:
            resp = await http.get(
                f"https://api.cloudflare.com/client/v4/accounts/{account_id}/pages/projects",
                headers={"Authorization": f"Bearer {token}"},
                params={"per_page": 1},
            )
            data = resp.json()
            ok = resp.status_code == 200 and data.get("success", False)
            if ok:
                results.append(CheckResult(
                    name="cloudflare_pages_access",
                    ok=True,
                    detail=f"API token can access Pages for account {account_id[:8]}… ✓",
                ))
            else:
                errors = data.get("errors", [])
                msg = errors[0].get("message", resp.text) if errors else resp.text
                results.append(CheckResult(
                    name="cloudflare_pages_access",
                    ok=False,
                    detail=f"CF Pages API returned {resp.status_code}: {msg}",
                    fix_hint=(
                        "Ensure CLOUDFLARE_API_TOKEN has 'Cloudflare Pages: Edit' permission "
                        "and CLOUDFLARE_ACCOUNT_ID matches the account that owns the token."
                    ),
                ))
    except Exception as e:
        results.append(CheckResult(
            name="cloudflare_pages_access",
            ok=False,
            detail=f"Request failed: {e}",
            fix_hint="Check network connectivity and CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID.",
        ))

    return results


async def check_stripe() -> list[CheckResult]:
    """Verify Stripe secret key is set and valid (lightweight balance check)."""
    from src.config import get_settings
    import httpx

    settings = get_settings()
    results: list[CheckResult] = []

    key = (settings.stripe_secret_key or "").strip()
    if not key:
        results.append(CheckResult(
            name="stripe_secret_key",
            ok=False,
            detail="STRIPE_SECRET_KEY is not set.",
            fix_hint="Set STRIPE_SECRET_KEY in your environment.",
        ))
        return results

    if not key.startswith(("sk_live_", "sk_test_")):
        results.append(CheckResult(
            name="stripe_secret_key",
            ok=False,
            detail="STRIPE_SECRET_KEY doesn't look like a Stripe key (expected sk_live_ or sk_test_).",
            fix_hint="Check STRIPE_SECRET_KEY value in your environment.",
        ))
        return results

    try:
        async with httpx.AsyncClient(timeout=10) as http:
            resp = await http.get(
                "https://api.stripe.com/v1/balance",
                headers={"Authorization": f"Bearer {key}"},
            )
            if resp.status_code == 200:
                results.append(CheckResult(
                    name="stripe_auth",
                    ok=True,
                    detail=f"Stripe key ({'live' if 'live' in key else 'test'}) authenticated ✓",
                ))
            else:
                results.append(CheckResult(
                    name="stripe_auth",
                    ok=False,
                    detail=f"Stripe returned {resp.status_code}: {resp.text[:200]}",
                    fix_hint="Verify STRIPE_SECRET_KEY is a valid, active Stripe secret key.",
                ))
    except Exception as e:
        results.append(CheckResult(
            name="stripe_auth",
            ok=False,
            detail=f"Request failed: {e}",
        ))

    webhook_secret = (settings.stripe_webhook_secret or "").strip()
    results.append(CheckResult(
        name="stripe_webhook_secret",
        ok=bool(webhook_secret),
        detail="Webhook secret configured ✓" if webhook_secret else "STRIPE_WEBHOOK_SECRET is not set.",
        fix_hint="Set STRIPE_WEBHOOK_SECRET to the secret from your Stripe webhook endpoint." if not webhook_secret else "",
    ))

    return results


async def check_redis() -> CheckResult:
    """Ping Redis."""
    from src.config import get_settings
    try:
        import redis.asyncio as aioredis
        settings = get_settings()
        r = aioredis.from_url(
            settings.redis_url,
            socket_connect_timeout=5,
            socket_timeout=5,
        )
        await r.ping()
        await r.aclose()
        return CheckResult(name="redis", ok=True, detail="Redis PING OK ✓")
    except Exception as e:
        return CheckResult(
            name="redis",
            ok=False,
            detail=f"Redis unreachable: {e}",
            fix_hint="Check REDIS_URL and that the Redis server is running.",
        )


async def check_database() -> CheckResult:
    """Run SELECT 1 using a one-off engine (NullPool) — never uses the API request pool."""
    try:
        from sqlalchemy import text
        from sqlalchemy.pool import NullPool
        from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine

        from src.config import get_settings

        s = get_settings()
        engine = create_async_engine(
            s.database_url,
            poolclass=NullPool,
            echo=s.debug,
            connect_args={"timeout": s.database_connect_timeout_seconds},
        )
        try:
            factory = async_sessionmaker(
                engine,
                class_=AsyncSession,
                expire_on_commit=False,
                autocommit=False,
                autoflush=False,
            )
            async with factory() as session:
                await session.execute(text("SELECT 1"))
        finally:
            await engine.dispose()
        return CheckResult(name="database", ok=True, detail="Database connection OK ✓")
    except Exception as e:
        return CheckResult(
            name="database",
            ok=False,
            detail=f"Database unreachable: {e}",
            fix_hint="Check DATABASE_URL and that PostgreSQL is running.",
        )


# ---------------------------------------------------------------------------
# Full diagnostic run
# ---------------------------------------------------------------------------

async def run_all() -> DiagnosticsReport:
    """Run all integration checks and return a combined report."""
    report = DiagnosticsReport()

    report.checks.append(await check_database())
    #await check_database()
    report.checks.append(await check_redis())
    #await check_redis()
    report.checks.extend(await check_github_app())
    #await check_github_app()

    from src.config import get_settings
    settings = get_settings()
    if settings.hosting_provider == "cloudflare_pages":
        report.checks.extend(await check_cloudflare())
        #await check_cloudflare()
    if settings.stripe_secret_key:
        report.checks.extend(await check_stripe())
        #await check_stripe()

    return report


async def run_and_log_warnings() -> DiagnosticsReport:
    """Run all checks and log a warning for every failure. Used on startup."""
    report = await run_all()
    if report.all_ok:
        logger.info("diagnostics_all_ok", checks=len(report.checks))
    else:
        for check in report.failures:
            logger.warning(
                "diagnostics_check_failed",
                check=check.name,
                detail=check.detail,
                fix_hint=check.fix_hint or "(see docs)",
            )
        logger.warning(
            "diagnostics_summary",
            failed=[c.name for c in report.failures],
            hint="Fix the above misconfigurations — some features may not work until resolved.",
        )
    return report
