"""Command audit log for active agent (0.1.19 Phase 5)."""

from __future__ import annotations

import hashlib
import json
import os
import stat
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Mapping

from iuve_connect_agents.shared.feature_flags import avatar_plan_audit_extended


def _utc_now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def _hash_text(value: str | None) -> str:
    data = (value or "").encode("utf-8")
    return hashlib.sha256(data).hexdigest()


class CommandAuditLogger:
    """Append-only JSONL audit log for command lifecycle events."""

    DEFAULT_DIR = Path.home() / ".config" / "iuve-connect-agent"
    AUDIT_FILENAME = "command-audit.jsonl"

    def __init__(self, data_dir: str | Path | None = None) -> None:
        base = Path(data_dir) if data_dir is not None else self.DEFAULT_DIR
        self._path = base / self.AUDIT_FILENAME

    @property
    def audit_path(self) -> Path:
        return self._path

    def log_command_attempt(
        self,
        *,
        command_id: str,
        command_type: str,
        capability: str = "",
        requested_by: str = "",
        approved_by: str = "",
        cwd: str = "",
        policy_decision: str = "allowed",
    ) -> str:
        return self._write(
            "command_attempt",
            command_id=command_id,
            command_type=command_type,
            capability=capability,
            requested_by=requested_by,
            approved_by=approved_by,
            cwd=cwd or os.getcwd(),
            policy_decision=policy_decision,
        )

    def log_command_denied(
        self,
        *,
        command_id: str,
        command_type: str,
        capability: str = "",
        requested_by: str = "",
        cwd: str = "",
        policy_decision: str = "denied",
        result_summary: str = "",
    ) -> str:
        return self._write(
            "command_denied",
            command_id=command_id,
            command_type=command_type,
            capability=capability,
            requested_by=requested_by,
            approved_by="",
            cwd=cwd or os.getcwd(),
            policy_decision=policy_decision,
            result_summary=result_summary,
        )

    def log_command_completed(
        self,
        *,
        command_id: str,
        command_type: str,
        capability: str = "",
        requested_by: str = "",
        approved_by: str = "local_user",
        cwd: str = "",
        exit_code: int = 0,
        stdout: str = "",
        stderr: str = "",
        result_summary: str = "",
        policy_decision: str = "allowed",
    ) -> str:
        return self._write(
            "command_completed",
            command_id=command_id,
            command_type=command_type,
            capability=capability,
            requested_by=requested_by,
            approved_by=approved_by,
            cwd=cwd or os.getcwd(),
            exit_code=exit_code,
            stdout_hash=_hash_text(stdout),
            stderr_hash=_hash_text(stderr),
            result_summary=result_summary,
            policy_decision=policy_decision,
        )

    def log_session_start(
        self,
        *,
        session_id: str,
        preset_key: str = "",
        bound_to_plan_id: str = "",
        title: str = "",
        ttl_seconds: int = 0,
        requested_by: str = "iuve_avatar",
    ) -> str:
        return self._write(
            "session_start",
            command_id=session_id,
            command_type="action_session",
            capability=preset_key,
            requested_by=requested_by,
            result_summary=title[:200],
            policy_decision="allowed",
            bound_to_plan_id=str(bound_to_plan_id or "").strip(),
            ttl_seconds=int(ttl_seconds),
        )

    def log_session_end(
        self,
        *,
        session_id: str,
        preset_key: str = "",
        bound_to_plan_id: str = "",
        reason: str = "plan_completed",
        requested_by: str = "iuve_avatar",
    ) -> str:
        return self._write(
            "session_end",
            command_id=session_id,
            command_type="action_session",
            capability=preset_key,
            requested_by=requested_by,
            result_summary=str(reason or "revoked")[:200],
            policy_decision="revoked",
            bound_to_plan_id=str(bound_to_plan_id or "").strip(),
        )

    def log_plan_created(
        self,
        *,
        plan_id: str,
        goal_summary: str,
        step_count: int,
        requested_by: str = "iuve_avatar_planner",
        step_id: str = "",
        command_type: str = "",
        confirm_gate_count: int = 0,
        planner_trace: Mapping[str, Any] | None = None,
        goal_route: Mapping[str, Any] | None = None,
        orchestrator: str = "",
    ) -> str:
        """Record plan materialization for multi-step audit correlation."""
        fields: dict[str, Any] = {
            "plan_id": str(plan_id or "").strip(),
            "goal_summary": str(goal_summary or "")[:500],
            "step_count": int(step_count),
            "requested_by": requested_by,
        }
        if orchestrator:
            fields["orchestrator"] = str(orchestrator)
        if goal_route:
            fields["goal_route"] = {
                str(k): v
                for k, v in dict(goal_route).items()
                if str(k) not in {"raw_html", "credentials", "secret"}
            }
        if avatar_plan_audit_extended():
            if step_id:
                fields["step_id"] = step_id
            if command_type:
                fields["command_type"] = command_type
            fields["confirm_gate_count"] = int(confirm_gate_count)
            if planner_trace:
                fields["planner_trace"] = {
                    str(k): v
                    for k, v in dict(planner_trace).items()
                    if str(k) not in {"raw_html", "credentials", "secret"}
                }
        return self._write("plan_created", **fields)

    def log_goal_routed(
        self,
        *,
        goal_text: str,
        goal_category: str,
        orchestrator: str,
        secondary_orchestrators: list[str] | None = None,
        confidence: float = 0.0,
        planner_type: str | None = None,
        session_preset: str | None = None,
        needs_clarification: bool = False,
        decision_id: str = "",
        requested_by: str = "goal_router",
    ) -> str:
        """Record goal classification and orchestrator selection."""
        return self._write(
            "goal_routed",
            command_id=str(decision_id or uuid.uuid4()),
            command_type="goal_route",
            capability=orchestrator,
            requested_by=requested_by,
            policy_decision="routed",
            result_summary=str(goal_text or "")[:500],
            goal_category=str(goal_category or ""),
            orchestrator=str(orchestrator or ""),
            secondary_orchestrators=list(secondary_orchestrators or []),
            goal_route_confidence=float(confidence),
            planner_type=planner_type,
            session_preset=session_preset,
            needs_clarification=bool(needs_clarification),
        )

    def log_device_apps_discovered(
        self,
        *,
        apps: list[dict[str, Any] | Mapping[str, Any]],
    ) -> str:
        """Audit entry when periodic scan discovers new .desktop applications."""
        names = [
            str(item.get("name") or item.get("slug") or "?")[:120]
            for item in apps
            if isinstance(item, (dict, Mapping))
        ]
        return self._write(
            "device_apps_discovered",
            command_id=str(uuid.uuid4()),
            command_type="device_scan",
            capability="device.inventory.apps",
            requested_by="device_scan_scheduler",
            result_summary=f"discovered {len(apps)} new app(s): {', '.join(names[:5])}",
            policy_decision="observed",
            discovered_count=len(apps),
            discovered_names=names[:20],
        )

    def log_session_covered_execution(
        self,
        *,
        command_id: str,
        command_type: str,
        capability: str = "",
        session_id: str,
        plan_id: str = "",
        requested_by: str = "iuve_avatar",
        reason: str = "covered_by_action_session",
    ) -> str:
        return self._write(
            "command_auto_executed",
            command_id=command_id,
            command_type=command_type,
            capability=capability,
            requested_by=requested_by,
            policy_decision="allowed",
            result_summary=str(reason or "covered_by_action_session")[:200],
            session_id=str(session_id or "").strip(),
            plan_id=str(plan_id or "").strip(),
            reason=str(reason or "covered_by_action_session"),
        )

    def log_command_failed(
        self,
        *,
        command_id: str,
        command_type: str,
        capability: str = "",
        requested_by: str = "",
        approved_by: str = "",
        cwd: str = "",
        exit_code: int = 1,
        stdout: str = "",
        stderr: str = "",
        result_summary: str = "",
        policy_decision: str = "allowed",
    ) -> str:
        return self._write(
            "command_failed",
            command_id=command_id,
            command_type=command_type,
            capability=capability,
            requested_by=requested_by,
            approved_by=approved_by,
            cwd=cwd or os.getcwd(),
            exit_code=exit_code,
            stdout_hash=_hash_text(stdout),
            stderr_hash=_hash_text(stderr),
            result_summary=result_summary,
            policy_decision=policy_decision,
        )

    def read_recent(self, n: int = 50) -> list[dict[str, Any]]:
        """Return up to *n* most recent audit records (newest first)."""
        if n <= 0 or not self._path.exists():
            return []
        try:
            lines = self._path.read_text(encoding="utf-8").splitlines()
            records: list[dict[str, Any]] = []
            for line in reversed(lines):
                line = line.strip()
                if not line:
                    continue
                try:
                    records.append(json.loads(line))
                except json.JSONDecodeError:
                    continue
                if len(records) >= n:
                    break
            return records
        except Exception:
            return []

    def _write(self, event: str, **fields: Any) -> str:
        audit_record_id = str(uuid.uuid4())
        record: dict[str, Any] = {
            "event": event,
            "audit_record_id": audit_record_id,
            "timestamp": _utc_now_iso(),
            "command_id": "",
            "requested_by": "",
            "approved_by": "",
            "capability": "",
            "command_type": "",
            "cwd": "",
            "exit_code": None,
            "stdout_hash": "",
            "stderr_hash": "",
            "result_summary": "",
            "policy_decision": "",
        }
        record.update({k: v for k, v in fields.items() if v is not None})
        self._path.parent.mkdir(parents=True, exist_ok=True)
        with open(self._path, "a", encoding="utf-8") as fh:
            fh.write(json.dumps(record, ensure_ascii=True) + "\n")
        self._enforce_0600_permissions()
        return audit_record_id

    def _enforce_0600_permissions(self) -> None:
        self._path.chmod(0o600)
        mode = stat.S_IMODE(self._path.stat().st_mode)
        if mode != 0o600:
            raise PermissionError(f"Expected 0o600 permissions, got {oct(mode)}")
