"""CLI product analytics via PostHog.

Analytics is opt-in by configuration:
- project API key is embedded in the CLI package
- DO_NOT_TRACK can disable it
- TU_ACTOR_TYPE can explicitly set actor type (human/agent/ci)

This module must never break CLI behavior. All failures are swallowed.
"""

from __future__ import annotations

import json
import os
import platform
import sys
import threading
import time
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Sequence
from uuid import uuid4

import httpx

from terminaluse.lib.cli.utils.credentials import get_credentials_path

_DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com"
_POSTHOG_PROJECT_API_KEY = "phc_uY7mDGoyS3pPSh1jNp0xrBuAx3Um7JnVjy9xHIjXl6j"
_ANALYTICS_ID_FILENAME = "analytics_id"
_ANALYTICS_SESSION_FILENAME = "analytics_session.json"
_SESSION_TTL_SECONDS = 1800

_TRACKED_EVENT_STARTED = "cli_command_started"
_TRACKED_EVENT_COMPLETED = "cli_command_completed"

# CLI commands registered in main.py
_TOP_LEVEL_COMMANDS = {
    "login",
    "logout",
    "whoami",
    "init",
    "deploy",
    "ls",
    "rollback",
    "logs",
    "agents",
    "tasks",
    "projects",
    "namespaces",
    "ns",
    "env",
    "fs",
    "filesystems",
    "keys",
}

# Subcommand groups (Typer apps)
_COMMAND_GROUPS = {"agents", "tasks", "projects", "namespaces", "ns", "env", "fs", "filesystems", "keys"}

_CI_ENV_MARKERS = {
    "CI",
    "GITHUB_ACTIONS",
    "BUILDKITE",
    "JENKINS_URL",
    "TF_BUILD",
    "TEAMCITY_VERSION",
    "CIRCLECI",
}
_CI_BOOLEAN_ENV_MARKERS = {"CI", "GITHUB_ACTIONS", "BUILDKITE", "TF_BUILD", "CIRCLECI"}
_AGENT_ENV_MARKERS = {
    "CODEX_HOME",
    "CLAUDECODE",
    "CLAUDE_CODE",
    "TERMINALUSE_AGENT_NAME",
    "TERMINALUSE_WORKFLOW_NAME",
}

_posthog_api_key: str | None = None
_distinct_id: str | None = None
_service_version: str = "unknown"
_session_id: str | None = None
_command_index: int = 1
_initialized = False
_enabled = False


@dataclass(frozen=True)
class ActorInfo:
    actor_type: str
    source: str
    confidence: str


def _is_truthy(value: str | None) -> bool:
    return (value or "").strip().lower() in {"1", "true", "yes", "on"}


def _is_disabled() -> bool:
    return _is_truthy(os.getenv("DO_NOT_TRACK"))


def _analytics_id_path() -> Path:
    return get_credentials_path().parent / _ANALYTICS_ID_FILENAME


def _analytics_session_path() -> Path:
    return get_credentials_path().parent / _ANALYTICS_SESSION_FILENAME


def _load_or_create_distinct_id() -> str | None:
    path = _analytics_id_path()
    try:
        if path.exists():
            existing = path.read_text().strip()
            if existing:
                return existing
        path.parent.mkdir(parents=True, exist_ok=True)
        generated = str(uuid4())
        path.write_text(generated)
        try:
            path.chmod(0o600)
        except OSError:
            pass
        return generated
    except OSError:
        return None


def _load_or_create_session() -> tuple[str, int]:
    session_id = str(uuid4())
    command_index = 1
    path = _analytics_session_path()
    now = time.time()
    ttl = _SESSION_TTL_SECONDS

    try:
        if path.exists():
            data = json.loads(path.read_text())
            existing_session = str(data.get("session_id", "")).strip()
            existing_last_seen = float(data.get("last_seen_at", 0))
            existing_command_index = int(data.get("command_index", 0))

            if existing_session and now - existing_last_seen <= ttl:
                session_id = existing_session
                command_index = max(existing_command_index + 1, 1)

        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(
            json.dumps(
                {
                    "session_id": session_id,
                    "last_seen_at": now,
                    "command_index": command_index,
                }
            )
        )
        try:
            path.chmod(0o600)
        except OSError:
            pass
    except Exception:
        pass

    return session_id, command_index


def _is_interactive() -> bool:
    try:
        return bool(sys.stdin.isatty() and sys.stdout.isatty())
    except Exception:
        return False


def _is_ci_environment() -> bool:
    for marker in _CI_ENV_MARKERS:
        value = os.getenv(marker)
        if not value:
            continue

        # Some CI markers are non-boolean metadata (e.g. Jenkins URL, TeamCity version).
        if marker not in _CI_BOOLEAN_ENV_MARKERS:
            return True

        if _is_truthy(value):
            return True

    return False


def _detect_actor_info() -> ActorInfo:
    explicit = (os.getenv("TU_ACTOR_TYPE") or "").strip().lower()
    if explicit in {"human", "agent", "ci"}:
        return ActorInfo(actor_type=explicit, source="explicit_env", confidence="high")

    if _is_ci_environment():
        return ActorInfo(actor_type="ci", source="ci_env", confidence="high")

    if any(os.getenv(marker) for marker in _AGENT_ENV_MARKERS):
        return ActorInfo(actor_type="agent", source="agent_env", confidence="medium")

    if not _is_interactive():
        return ActorInfo(actor_type="agent", source="non_interactive", confidence="low")

    return ActorInfo(actor_type="human", source="interactive_tty", confidence="medium")


def init_cli_analytics(service_version: str = "unknown") -> bool:
    """Initialize PostHog analytics for the CLI."""
    global _posthog_api_key, _distinct_id, _service_version
    global _session_id, _command_index, _initialized, _enabled

    if _initialized:
        return _enabled

    _initialized = True
    _service_version = service_version

    if _is_disabled():
        return False

    api_key = _POSTHOG_PROJECT_API_KEY.strip()
    if not api_key:
        return False

    distinct_id = _load_or_create_distinct_id()
    if not distinct_id:
        return False

    _posthog_api_key = api_key
    _distinct_id = distinct_id
    _session_id, _command_index = _load_or_create_session()
    _enabled = True
    return True


def _normalize_exit_code(value: object) -> int:
    if value is None:
        return 0
    if isinstance(value, int):
        return value
    return 1


def parse_cli_command(argv: Sequence[str]) -> tuple[str, list[str]]:
    """Extract a sanitized command path and flags from argv."""
    tokens = list(argv)
    flags = sorted({token.split("=", 1)[0] for token in tokens if token.startswith("-")})

    root: str | None = None
    root_idx = -1
    for idx, token in enumerate(tokens):
        if token in _TOP_LEVEL_COMMANDS:
            root = token
            root_idx = idx
            break

    if not root:
        for token in tokens:
            if token in {"--help", "-h"}:
                return "help", flags
            if token in {"--version", "-v"}:
                return "version", flags
        return "unknown", flags

    if root not in _COMMAND_GROUPS:
        return root, flags

    for token in tokens[root_idx + 1 :]:
        if token.startswith("-"):
            continue
        return f"{root}.{token}", flags

    return root, flags


def _infer_validation_stage(error_code: str | None) -> str | None:
    if not error_code:
        return None
    if error_code in {"VALIDATION_ERROR", "INVALID_INPUT"}:
        return "validation"
    if error_code in {"AUTH_REQUIRED", "ACCESS_DENIED"}:
        return "auth"
    if error_code == "NOT_FOUND":
        return "lookup"
    if error_code in {"SERVER_ERROR"}:
        return "api"
    if error_code == "INTERRUPTED":
        return "interrupt"
    if error_code in {"INTERNAL_ERROR", "UNKNOWN_ERROR"}:
        return "internal"
    return "unknown"


def _base_properties(
    command: str, flags: list[str] | None, run_id: str | None, help_requested: bool
) -> dict[str, object]:
    actor_info = _detect_actor_info()
    return {
        "$lib": "terminaluse-cli",
        "cli.command": command,
        "cli.command.root": command.split(".", 1)[0],
        "cli.flags": flags or [],
        "cli.help_requested": help_requested,
        "cli.version": _service_version,
        "cli.run_id": run_id,
        "cli.session_id": _session_id,
        "cli.command_index": _command_index,
        "actor.type": actor_info.actor_type,
        "actor.source": actor_info.source,
        "actor.confidence": actor_info.confidence,
        "runtime.interactive": _is_interactive(),
        "python.version": platform.python_version(),
        "python.implementation": platform.python_implementation(),
        "os.name": platform.system().lower(),
        "ci": _is_truthy(os.getenv("CI")),
    }


def _send_event(event_name: str, properties: dict[str, object]) -> None:
    payload = {
        "api_key": _posthog_api_key,
        "event": event_name,
        "distinct_id": _distinct_id,
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "properties": properties,
    }

    def _post_capture_payload(payload_to_send: dict[str, object]) -> None:
        try:
            httpx.post(
                f"{_DEFAULT_POSTHOG_HOST}/capture/",
                json=payload_to_send,
                timeout=1.5,
            )
        except Exception:
            # Best-effort analytics only.
            pass

    try:
        thread = threading.Thread(
            target=_post_capture_payload,
            args=(payload,),
            name="tu-posthog-capture",
            daemon=True,
        )
        thread.start()
    except Exception:
        # Best-effort analytics only.
        pass


def track_cli_command_started(
    command: str,
    flags: list[str] | None = None,
    run_id: str | None = None,
    help_requested: bool = False,
) -> None:
    """Send a command-start event to PostHog."""
    global _initialized

    if not _initialized:
        init_cli_analytics()
    if not _enabled or not _posthog_api_key or not _distinct_id:
        return

    properties = _base_properties(command=command, flags=flags, run_id=run_id, help_requested=help_requested)
    _send_event(_TRACKED_EVENT_STARTED, properties)


def track_cli_command(
    command: str,
    exit_code: object,
    duration_ms: int,
    flags: list[str] | None = None,
    error_code: str | None = None,
    run_id: str | None = None,
    help_requested: bool = False,
    hint_shown: bool | None = None,
) -> None:
    """Send a CLI command completion event to PostHog."""
    global _initialized

    if not _initialized:
        init_cli_analytics()
    if not _enabled or not _posthog_api_key or not _distinct_id:
        return

    normalized_exit = _normalize_exit_code(exit_code)

    properties = _base_properties(command=command, flags=flags, run_id=run_id, help_requested=help_requested)
    properties.update(
        {
            "cli.success": normalized_exit == 0,
            "cli.exit_code": normalized_exit,
            "cli.duration_ms": max(duration_ms, 0),
        }
    )
    if error_code:
        properties["cli.error_code"] = error_code
        stage = _infer_validation_stage(error_code)
        if stage:
            properties["cli.validation_stage"] = stage
    if hint_shown is not None:
        properties["cli.hint_shown"] = hint_shown

    _send_event(_TRACKED_EVENT_COMPLETED, properties)
