"""Deception response engine."""

from __future__ import annotations

import logging
from pathlib import Path
from typing import Any


class DeceptionEngine:
    """Generates operational deception actions from risk level."""

    def __init__(self, config: dict[str, Any]) -> None:
        self.config = config
        self.logger = logging.getLogger("cyberflage.deception")
        safety_cfg = config.get("safety", {})
        self.destructive_actions_enabled = bool(safety_cfg.get("destructive_actions_enabled", False))
        self._last_backup_path: Path | None = None
        self.mode = "MONITORING"
        self._last_swap_details: dict[str, str] = {}

    def evaluate(self, level: str) -> str:
        """Return an action message for given level."""
        safe_messages = {
            "LOW": "Continue passive monitoring.",
            "MEDIUM": "Increase decoy visibility.",
            "HIGH": "Deploy active deception traps.",
            "CRITICAL": "Escalate to full deception protocol (safe mode).",
        }
        destructive_messages = {
            "LOW": "Continue passive monitoring.",
            "MEDIUM": "Increase decoy visibility.",
            "HIGH": "Deploy active deception traps.",
            "CRITICAL": "Execute critical containment protocol.",
        }
        messages = destructive_messages if self.destructive_actions_enabled else safe_messages
        return messages.get(level, messages["LOW"])

    def _resolve_paths(self) -> tuple[Path, Path]:
        protected_paths = self.config.get("protected_paths", [])
        if not isinstance(protected_paths, list) or not protected_paths:
            raise ValueError("No protected paths configured.")
        real_path = Path(str(protected_paths[0])).expanduser().resolve(strict=False)
        decoy_path = Path(str(self.config.get("decoy_path", ""))).expanduser().resolve(strict=False)
        if not str(decoy_path).strip():
            raise ValueError("No decoy path configured.")
        if real_path.parent != decoy_path.parent:
            raise ValueError("Real and decoy paths must be in same directory for safe swap")
        return real_path, decoy_path

    @staticmethod
    def _next_backup_path(real_path: Path) -> Path:
        base = real_path.with_name(f"{real_path.name}_backup")
        if not base.exists():
            return base
        index = 1
        while True:
            candidate = real_path.with_name(f"{real_path.name}_backup_{index}")
            if not candidate.exists():
                return candidate
            index += 1

    @staticmethod
    def _latest_backup_path(real_path: Path) -> Path | None:
        parent = real_path.parent
        base_name = f"{real_path.name}_backup"
        candidates: list[tuple[int, Path]] = []
        for item in parent.iterdir():
            if not item.is_dir():
                continue
            if item.name == base_name:
                candidates.append((0, item))
            elif item.name.startswith(f"{base_name}_"):
                suffix = item.name.removeprefix(f"{base_name}_")
                if suffix.isdigit():
                    candidates.append((int(suffix), item))
        if not candidates:
            return None
        candidates.sort(key=lambda pair: pair[0], reverse=True)
        return candidates[0][1]

    @staticmethod
    def _next_decoy_archive_path(decoy_path: Path) -> Path:
        old_candidate = decoy_path.with_name(f"{decoy_path.name}_old")
        if not old_candidate.exists():
            return old_candidate
        backup_candidate = decoy_path.with_name(f"{decoy_path.name}_backup")
        if not backup_candidate.exists():
            return backup_candidate
        index = 1
        while True:
            candidate = decoy_path.with_name(f"{decoy_path.name}_backup_{index}")
            if not candidate.exists():
                return candidate
            index += 1

    def _rollback_swap(
        self,
        *,
        real_path: Path,
        decoy_path: Path,
        backup_path: Path,
        moved_real: bool,
        moved_decoy: bool,
    ) -> None:
        self.logger.warning("[DECEPTION] Rollback triggered")
        rollback_ok = True

        # If decoy moved into real_path, move it back first.
        if moved_decoy and real_path.exists() and not decoy_path.exists():
            try:
                real_path.rename(decoy_path)
            except Exception as exc:
                rollback_ok = False
                self.logger.error("[DECEPTION] Rollback failed while restoring decoy path: %s", exc)

        # Restore original real folder from backup.
        if moved_real and backup_path.exists() and not real_path.exists():
            try:
                backup_path.rename(real_path)
            except Exception as exc:
                rollback_ok = False
                self.logger.error("[DECEPTION] Rollback failed while restoring real path: %s", exc)

        if rollback_ok:
            self.logger.info("[DECEPTION] Rollback completed successfully")

    def _activate_decoy(self) -> str:
        """Safely swap real folder with decoy folder using rename-only operations."""
        self.logger.info("[DECEPTION] Attempting swap")
        try:
            real_path, decoy_path = self._resolve_paths()
            if not real_path.exists():
                raise FileNotFoundError(f"Protected path not found: {real_path}")
            if not decoy_path.exists():
                raise FileNotFoundError(f"Decoy path not found: {decoy_path}")

            backup_path = self._next_backup_path(real_path)
            self._last_swap_details = {
                "real_path": str(real_path),
                "decoy_path": str(decoy_path),
                "backup_path": str(backup_path),
            }
            moved_real = False
            moved_decoy = False
            try:
                real_path.rename(backup_path)
                moved_real = True
                decoy_path.rename(real_path)
                moved_decoy = True

                if not real_path.exists() or not backup_path.exists():
                    raise RuntimeError("Decoy swap verification failed")
            except Exception as swap_error:
                self._rollback_swap(
                    real_path=real_path,
                    decoy_path=decoy_path,
                    backup_path=backup_path,
                    moved_real=moved_real,
                    moved_decoy=moved_decoy,
                )
                raise swap_error

            self._last_backup_path = backup_path
            self.logger.info("[DECEPTION] Swap success")
            return "Decoy environment activated successfully."
        except Exception as exc:
            self.logger.error("[DECEPTION] Decoy activation failed: %s", exc)
            return f"Decoy activation failed: {exc}"

    def restore_real_environment(self) -> str:
        """Restore original real environment from latest backup using rename-only operations."""
        if not self.destructive_actions_enabled:
            self.logger.info("[SIMULATION] Restore skipped (safe mode)")
            return "[SIMULATION] Restore skipped (safe mode)"

        try:
            real_path, decoy_path = self._resolve_paths()
            backup_path = self._last_backup_path
            if backup_path is None or not backup_path.exists():
                backup_path = self._latest_backup_path(real_path)
            if backup_path is None:
                raise FileNotFoundError("No backup folder found for restore.")

            # Move active decoy out of real_path back to decoy_path when possible.
            if real_path.exists():
                if decoy_path.exists():
                    archive_path = self._next_decoy_archive_path(decoy_path)
                    decoy_path.rename(archive_path)
                    self.logger.info("[DECEPTION] Existing decoy archived to: %s", archive_path)
                real_path.rename(decoy_path)

            backup_path.rename(real_path)
            if not real_path.exists():
                raise RuntimeError("Restore verification failed")

            self.mode = "MONITORING"
            self._last_backup_path = None
            self.logger.info("[DECEPTION] Restore executed")
            return "Real environment restored successfully."
        except Exception as exc:
            self.logger.error("[DECEPTION] Restore failed: %s", exc)
            return f"Restore failed: {exc}"

    def execute(self, level: str) -> str:
        """Evaluate and optionally execute decoy activation at HIGH/CRITICAL risk."""
        action = self.evaluate(level)
        final_mode = "MONITORING" if level == "LOW" else "ALERT"

        if level not in {"HIGH", "CRITICAL"}:
            self.mode = final_mode
            return action

        if not self.destructive_actions_enabled:
            self.logger.info("[SIMULATION] Decoy activation skipped (safe mode)")
            self.mode = final_mode
            return f"{action} [SIMULATION] Decoy activation skipped (safe mode)"

        try:
            real_path, decoy_path = self._resolve_paths()
            backup_path = self._next_backup_path(real_path)
            self._last_swap_details = {
                "real_path": str(real_path),
                "decoy_path": str(decoy_path),
                "backup_path": str(backup_path),
            }
            if self._last_backup_path is not None or self._latest_backup_path(real_path) is not None:
                final_mode = "DECOY_ACTIVE"
                self.mode = final_mode
                return (
                    f"{action}\n"
                    "Decoy already active.\n"
                    "System is currently in DECOY_ACTIVE mode.\n"
                    "Use 'cyberflage restore' to revert."
                )
        except Exception as exc:
            # If pre-check fails, let normal activation flow generate user-readable error.
            self.logger.debug("Pre-check failed: %s", exc)

        details = self.get_last_swap_details()
        if details:
            print("[PREVIEW] About to swap:")
            print(f"- {details.get('real_path', '-')} -> {details.get('backup_path', '-')}")
            print(f"- {details.get('decoy_path', '-')} -> {details.get('real_path', '-')}")

        execution_result = self._activate_decoy()
        if execution_result == "Decoy environment activated successfully.":
            final_mode = "DECOY_ACTIVE"
        else:
            final_mode = "ALERT"

        self.mode = final_mode
        return f"{action} {execution_result}"

    def get_last_swap_details(self) -> dict[str, str]:
        """Return latest decoy swap details for CLI visibility."""
        return dict(self._last_swap_details)
