"""
Cross-Platform Automation Workflow Engine
"""

import os
import subprocess
import sys
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Any

import yaml

try:
    from yaml import CSafeLoader as SafeLoader
except ImportError:
    from yaml import SafeLoader

from navig.console_helper import error, info, warning
from navig.core.safe_eval import safe_eval


@dataclass
class WorkflowStep:
    action: str
    args: dict[str, Any]
    platform_override: dict[str, Any] | None = None
    capture: str | None = None
    if_condition: str | None = None


@dataclass
class Workflow:
    name: str
    steps: list[WorkflowStep]
    description: str = ""
    variables: dict[str, str] = None


class WorkflowEngine:
    def __init__(self):
        self._navig_root = Path(__file__).parent.parent.parent
        self._workflows_dir = self._navig_root / "workflows"
        self._workflows_dir.mkdir(exist_ok=True)
        self._workflow_cache: dict[str, tuple[float, Workflow]] = {}

        # Lazy load adapters
        self._ahk = None
        self._linux = None
        self._macos = None

    @property
    def ahk(self):
        if not self._ahk and sys.platform == "win32":
            from navig.adapters.automation.ahk import AHKAdapter

            self._ahk = AHKAdapter()
        return self._ahk

    @property
    def linux(self):
        if not self._linux and sys.platform == "linux":
            from navig.adapters.automation.linux import LinuxAdapter

            self._linux = LinuxAdapter()
        return self._linux

    @property
    def macos(self):
        if not self._macos and sys.platform == "darwin":
            from navig.adapters.automation.macos import MacOSAdapter

            self._macos = MacOSAdapter()
        return self._macos

    @property
    def adapter(self):
        """Get the appropriate adapter for current platform."""
        if sys.platform == "win32":
            return self.ahk
        elif sys.platform == "linux":
            return self.linux
        elif sys.platform == "darwin":
            return self.macos
        return None

    def load_workflow(self, name: str) -> Workflow | None:
        """Load workflow from YAML file."""
        # Check standard locations
        possible_paths = [
            self._workflows_dir / f"{name}.yaml",
            self._workflows_dir / f"{name}.yml",
            Path.home() / ".navig" / "workflows" / f"{name}.yaml",
        ]

        target_path = None
        for p in possible_paths:
            if p.exists():
                target_path = p
                break

        if not target_path:
            return None

        try:
            mtime = os.stat(target_path).st_mtime
            if name in self._workflow_cache:
                cached_mtime, cached_wf = self._workflow_cache[name]
                if mtime == cached_mtime:
                    return cached_wf

            with open(target_path, encoding="utf-8") as f:
                data = yaml.load(f, Loader=SafeLoader)

            steps = []
            for s in data.get("steps", []):
                steps.append(
                    WorkflowStep(
                        action=s.get("action"),
                        args=s.get("args", {}),
                        platform_override=s.get("platform", None),
                        capture=s.get("capture", None),
                        if_condition=s.get("if", None),
                    )
                )

            wf = Workflow(
                name=data.get("name", name),
                description=data.get("description", ""),
                variables=data.get("variables", {}),
                steps=steps,
            )
            self._workflow_cache[name] = (mtime, wf)
            return wf
        except Exception as e:
            error(f"Failed to load workflow {name}: {e}")
            return None

    def execute_workflow(self, workflow: Workflow, variables: dict[str, str] = None):
        """Execute a cross-platform workflow."""
        info(f"Executing workflow: {workflow.name}")

        # Merge variables
        current_vars = workflow.variables.copy() if workflow.variables else {}
        if variables:
            current_vars.update(variables)

        for i, step in enumerate(workflow.steps):
            action = step.action
            args = step.args.copy()

            # Resolve variables in args
            for k, v in args.items():
                if isinstance(v, str) and "{{" in v:
                    for var_k, var_v in current_vars.items():
                        v = v.replace(f"{{{{{var_k}}}}}", str(var_v))
                    args[k] = v

            # Check Condition
            if step.if_condition:
                cond = step.if_condition

                # Use safe evaluation with variables map

                # Prepare variables for eval (convert all to appropriate types if possible, otherwise strings)
                eval_vars = current_vars.copy()

                # Helper: smart cast? For now just pass strings/dicts as is
                # 'true', 'false' strings -> booleans?
                # Let's rely on standard python types in variables

                try:
                    result = safe_eval(cond, eval_vars)
                    if not result:
                        info(f"Skipping step {i + 1} (condition false): {cond}")
                        continue
                except Exception as e:
                    warning(f"Condition evaluation failed '{cond}': {e}. Skipping step.")
                    continue

            # Platform overrides
            if step.platform_override:
                platform_key = "windows" if sys.platform == "win32" else sys.platform
                if platform_key in step.platform_override:
                    override = step.platform_override[platform_key]
                    if "action" in override:
                        action = override["action"]
                    if "args" in override:
                        override_args = override["args"].copy()
                        for k, v in override_args.items():
                            if isinstance(v, str) and "{{" in v:
                                for var_k, var_v in current_vars.items():
                                    v = v.replace(f"{{{{{var_k}}}}}", str(var_v))
                                override_args[k] = v
                        args.update(override_args)

            # Execute Action
            result = self._execute_action(action, args)

            # Capture Output
            if step.capture and result is not None:
                current_vars[step.capture] = str(result)
                info(f"Captured variable '{step.capture}': {result}")

        return current_vars

    def _execute_action(self, action: str, args: dict[str, Any]) -> Any:
        """Dispatch action to appropriate adapter."""
        # Platform-independent actions
        if action == "wait":
            time.sleep(float(args.get("seconds", 1.0)))
            return True

        if action == "run_command":
            cmd = args.get("command")
            if cmd:
                import shlex

                if sys.platform == "win32":
                    # On Windows, use cmd /c with the command as a single string
                    # argument to avoid shell=True while preserving cmd.exe features
                    run_args = ["cmd", "/c", cmd]
                else:
                    run_args = shlex.split(cmd)
                res = subprocess.run(run_args, capture_output=True, text=True)
                if res.returncode == 0:
                    return res.stdout.strip()
            return ""

        # Get platform-specific adapter
        adapter = self.adapter
        if not adapter or not adapter.is_available():
            warning(f"Platform {sys.platform} automation not available.")
            return None

        # Dispatch to adapter methods
        if action == "open_app":
            return adapter.open_app(args.get("target", ""))
        elif action == "click":
            return adapter.click(args.get("x"), args.get("y"), args.get("button", "left"))
        elif action == "type":
            return adapter.type_text(args.get("text"), args.get("delay", 50))
        elif action == "send":
            return adapter.send_keys(args.get("keys"))
        elif action == "mouse_move":
            return adapter.mouse_move(args.get("x"), args.get("y"), args.get("speed", 2))
        elif action == "get_focused_window":
            info_obj = adapter.get_focused_window()
            return info_obj.to_dict() if info_obj else None
        elif action == "activate_window":
            return adapter.activate_window(args.get("selector"))
        elif action == "close_window":
            return adapter.close_window(args.get("selector"))
        elif action == "move_window":
            return adapter.move_window(
                args.get("selector"),
                args.get("x"),
                args.get("y"),
                args.get("width"),
                args.get("height"),
            )
        elif action == "resize_window":
            # Some adapters may not have resize_window, use move_window with current pos
            if hasattr(adapter, "resize_window"):
                return adapter.resize_window(
                    args.get("selector"), args.get("width"), args.get("height")
                )
            else:
                # Get current window location and resize
                return adapter.move_window(
                    args.get("selector"), 0, 0, args.get("width"), args.get("height")
                )
        elif action == "maximize_window":
            return adapter.maximize_window(args.get("selector"))
        elif action == "minimize_window":
            return adapter.minimize_window(args.get("selector"))
        elif action == "snap_window":
            return adapter.snap_window(args.get("selector"), args.get("position"))
        elif action == "get_clipboard":
            return adapter.get_clipboard()
        elif action == "set_clipboard":
            return adapter.set_clipboard(args.get("text", ""))
        elif action == "wait_for":
            # Wait for condition
            check_type = args.get("type", "window")
            target = args.get("target", "")
            timeout = float(args.get("timeout", 30.0))
            start = time.time()

            if check_type == "window":
                # Poll for window existence
                while time.time() - start < timeout:
                    # Try to activate to check existence
                    result = adapter.activate_window(target)
                    if result and result.success:
                        return True
                    time.sleep(0.5)
                return False
            return False
        elif action == "read_text":
            # This is AHK-specific, not all platforms support it
            if hasattr(adapter, "read_text"):
                text = adapter.read_text(args.get("selector"), control_id=args.get("control", ""))
                if text:
                    info(f"Read text: {text}")
                return text
            else:
                warning("read_text not supported on this platform")
                return None
        else:
            warning(f"Unknown action: {action}")
            return None
