"""alice diagnose — Run setup diagnostics and display a pass/fail checklist."""

from __future__ import annotations

import shutil
import socket
import ssl
import sys
from pathlib import Path

import boto3
import click

from alice_cli.console import err_console
from alice_cli.constants import get_memory_bucket
from alice_cli.models import CLIConfig, DoctorCheck
from alice_cli.teaparty.audio_locker import resolve_audio_bucket

# ── Individual Doctor Checks ──────────────────────────────────────────


def _check_python_version() -> DoctorCheck:
    """Check that Python version is 3.12 or higher."""
    version = sys.version_info
    version_str = f"{version.major}.{version.minor}.{version.micro}"
    if version >= (3, 12):
        return DoctorCheck(
            name="Python version",
            passed=True,
            detail=f"Python {version_str}",
            hint="",
        )
    return DoctorCheck(
        name="Python version",
        passed=False,
        detail=f"Python {version_str} (requires 3.12+)",
        hint="Upgrade Python to 3.12 or higher: https://www.python.org/downloads/",
    )


def _check_boto3() -> DoctorCheck:
    """Check that boto3 is importable and report its version."""
    try:
        return DoctorCheck(
            name="boto3 package",
            passed=True,
            detail=f"boto3 {boto3.__version__}",
            hint="",
        )
    except ImportError:
        return DoctorCheck(
            name="boto3 package",
            passed=False,
            detail="boto3 not found",
            hint="Install boto3: pip install boto3",
        )


def _check_aws_cli() -> DoctorCheck:
    """Check that the aws CLI is available on PATH."""
    aws_path = shutil.which("aws")
    if aws_path:
        return DoctorCheck(
            name="AWS CLI",
            passed=True,
            detail=f"Found at {aws_path}",
            hint="",
        )
    return DoctorCheck(
        name="AWS CLI",
        passed=False,
        detail="aws CLI not found on PATH",
        hint="Install the AWS CLI: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html",
    )


def _check_stored_credentials() -> DoctorCheck:
    """Check that stored credentials exist at ~/.alice/credentials.json."""
    creds_path = Path.home() / ".alice" / "credentials.json"
    if creds_path.exists():
        return DoctorCheck(
            name="Stored credentials",
            passed=True,
            detail=str(creds_path),
            hint="",
        )
    return DoctorCheck(
        name="Stored credentials",
        passed=False,
        detail=f"{creds_path} not found",
        hint="Run 'alice auth --profile <PROFILE>' or 'alice auth --api-key <KEY>' to store credentials.",  # noqa: E501
    )


def _check_sso_session(config: CLIConfig) -> DoctorCheck:
    """Check that the SSO session is valid by calling sts get-caller-identity."""
    if not config.profile:
        return DoctorCheck(
            name="SSO session",
            passed=False,
            detail="No profile configured (using API key or no auth)",
            hint="Set --profile or AWS_PROFILE to use SSO authentication.",
        )
    try:
        session = boto3.Session(profile_name=config.profile, region_name=config.region)
        sts = session.client("sts")
        identity = sts.get_caller_identity()
        arn = identity.get("Arn", "unknown")
        return DoctorCheck(
            name="SSO session",
            passed=True,
            detail=f"Identity: {arn}",
            hint="",
        )
    except Exception as exc:
        return DoctorCheck(
            name="SSO session",
            passed=False,
            detail=str(exc),
            hint=f"Run: aws sso login --profile {config.profile} (or 'alice auth --flush' to reset stored credentials)",  # noqa: E501
        )


def _check_bedrock_endpoint(region: str) -> DoctorCheck:
    """Check network connectivity to the Bedrock endpoint via HTTPS."""
    hostname = f"bedrock-runtime.{region}.amazonaws.com"
    try:
        ctx = ssl.create_default_context()
        with (
            socket.create_connection((hostname, 443), timeout=5) as sock,
            ctx.wrap_socket(sock, server_hostname=hostname),
        ):
            pass
        return DoctorCheck(
            name="Bedrock endpoint",
            passed=True,
            detail=f"Connected to {hostname}:443",
            hint="",
        )
    except Exception as exc:
        return DoctorCheck(
            name="Bedrock endpoint",
            passed=False,
            detail=f"Cannot reach {hostname}: {exc}",
            hint="Check your network connection and ensure the Bedrock endpoint is accessible from your network.",  # noqa: E501
        )


def _check_s3_bucket(config: CLIConfig) -> DoctorCheck:
    """Check that the Cloud Locker S3 bucket is accessible via HEAD request."""
    bucket = get_memory_bucket(config.namespace, config.environment)
    try:
        if config.profile:
            session = boto3.Session(profile_name=config.profile, region_name=config.region)
            s3 = session.client("s3")
        elif config.bedrock_secret_key:
            s3 = boto3.client(
                "s3",
                region_name=config.region,
                aws_access_key_id=config.bedrock_access_key,
                aws_secret_access_key=config.bedrock_secret_key,
            )
        else:
            return DoctorCheck(
                name="S3 bucket access",
                passed=False,
                detail="No credentials available to check S3",
                hint="Run 'alice auth' first to configure credentials.",
            )
        s3.head_bucket(Bucket=bucket)
        return DoctorCheck(
            name="S3 bucket access",
            passed=True,
            detail=f"Bucket '{bucket}' is accessible",
            hint="",
        )
    except Exception as exc:
        return DoctorCheck(
            name="S3 bucket access",
            passed=False,
            detail=f"Cannot access bucket '{bucket}': {exc}",
            hint="Ensure your credentials have S3 access to the Cloud Locker bucket. Try 'alice auth --flush' then re-authenticate.",  # noqa: E501
        )


def _check_locker_prefix(config: CLIConfig, username: str) -> DoctorCheck:
    """Verify the user can access their own locker prefix via ListObjects."""
    bucket = get_memory_bucket(config.namespace, config.environment)
    prefix = f"{config.namespace}/{config.environment}/{username}/"
    try:
        if config.profile:
            session = boto3.Session(profile_name=config.profile, region_name=config.region)
            s3 = session.client("s3")
        elif config.bedrock_secret_key:
            s3 = boto3.client(
                "s3",
                region_name=config.region,
                aws_access_key_id=config.bedrock_access_key,
                aws_secret_access_key=config.bedrock_secret_key,
            )
        else:
            return DoctorCheck(
                name="Locker prefix access",
                passed=False,
                detail="No credentials available to check prefix",
                hint="Run 'alice auth' first to configure credentials.",
            )
        s3.list_objects_v2(Bucket=bucket, Prefix=prefix, MaxKeys=1)
        return DoctorCheck(
            name="Locker prefix access",
            passed=True,
            detail=f"Can list objects under '{prefix}'",
            hint="",
        )
    except Exception as exc:
        return DoctorCheck(
            name="Locker prefix access",
            passed=False,
            detail=f"Cannot list prefix '{prefix}': {exc}",
            hint="Ensure your IAM policy grants access to your locker prefix. Try 'alice auth --flush' then re-authenticate.",  # noqa: E501
        )


def _check_audio_bucket(config: CLIConfig) -> DoctorCheck:
    """Check that the Audio_Bucket is accessible via HeadBucket."""
    bucket = resolve_audio_bucket(config)
    try:
        if config.profile:
            session = boto3.Session(profile_name=config.profile, region_name=config.region)
            s3 = session.client("s3")
        elif config.bedrock_secret_key:
            s3 = boto3.client(
                "s3",
                region_name=config.region,
                aws_access_key_id=config.bedrock_access_key,
                aws_secret_access_key=config.bedrock_secret_key,
            )
        else:
            return DoctorCheck(
                name="Audio bucket access",
                passed=False,
                detail="No credentials available to check Audio_Bucket",
                hint="Run 'alice auth' first to configure credentials.",
            )
        s3.head_bucket(Bucket=bucket)
        return DoctorCheck(
            name="Audio bucket access",
            passed=True,
            detail=f"Audio_Bucket '{bucket}' is accessible",
            hint="",
        )
    except Exception as exc:
        return DoctorCheck(
            name="Audio bucket access",
            passed=False,
            detail=f"Cannot access Audio_Bucket '{bucket}': {exc}",
            hint="Ensure your credentials have s3:PutObject and s3:GetObject permissions on the Audio_Bucket.",  # noqa: E501
        )


# ── Display helpers ───────────────────────────────────────────────────


def display_checks(checks: list[DoctorCheck]) -> None:
    """Display each check result with green checkmark or red cross on stderr."""
    from rich.markup import escape

    err_console.print()
    for check in checks:
        if check.passed:
            err_console.print(f"  [green]✓[/green] {escape(check.name)}: {escape(check.detail)}")
        else:
            err_console.print(f"  [red]✗[/red] {escape(check.name)}: {escape(check.detail)}")
            if check.hint:
                err_console.print(f"    [yellow]↳ {escape(check.hint)}[/yellow]")


def display_summary(checks: list[DoctorCheck]) -> None:
    """Display a summary line with passed/total counts."""
    passed = sum(1 for c in checks if c.passed)
    total = len(checks)
    if passed == total:
        style = "green"
    elif passed == 0:
        style = "red"
    else:
        style = "yellow"
    err_console.print()
    err_console.print(f"  [{style}]{passed}/{total} checks passed[/{style}]")


# ── Resolve JHED for prefix check ────────────────────────────────────


def _resolve_username(config: CLIConfig) -> str | None:
    """Try to determine the user's username for the locker prefix check."""
    try:
        from alice_cli.auth import detect_identity_from_key, detect_username

        if config.bedrock_secret_key and not config.profile:
            identity = detect_identity_from_key(config.bedrock_access_key)
            return identity if identity != "unknown" else None
        if config.profile:
            result = detect_username(config.profile, config.region)
            return result.username
    except Exception:
        pass
    return None


# ── Click command ─────────────────────────────────────────────────────


@click.command("diagnose")
@click.pass_context
def diagnose_cmd(ctx: click.Context) -> None:
    """Run diagnostic checks and verify setup."""
    config: CLIConfig = ctx.obj["config"]

    err_console.print("[bold]ALiCE Diagnostics[/bold]")

    checks: list[DoctorCheck] = []

    # 1. Python version
    checks.append(_check_python_version())

    # 2. boto3 package
    checks.append(_check_boto3())

    # 3. AWS CLI on PATH
    checks.append(_check_aws_cli())

    # 4. Stored credentials
    checks.append(_check_stored_credentials())

    # 5. SSO session (only meaningful with a profile)
    checks.append(_check_sso_session(config))

    # 6. Bedrock endpoint connectivity
    checks.append(_check_bedrock_endpoint(config.region))

    # 7. S3 bucket access
    checks.append(_check_s3_bucket(config))

    # 8. Locker prefix access (needs JHED)
    username = _resolve_username(config)
    if username:
        checks.append(_check_locker_prefix(config, username))
    else:
        checks.append(
            DoctorCheck(
                name="Locker prefix access",
                passed=False,
                detail="Could not determine JHED for prefix check",
                hint="Run 'alice auth' to authenticate, then retry.",
            )
        )

    # 9. Audio bucket access
    checks.append(_check_audio_bucket(config))

    # Display results
    display_checks(checks)
    display_summary(checks)
