from __future__ import annotations

import re

from app.modules.agent.engine.orchestrator.actions.common import ActionSupport
from app.modules.agent.engine.orchestrator.execution_context import ExecutionContext
from app.modules.agent.engine.orchestrator.models import ArtifactType


class EditActions(ActionSupport):
    def resolve_target(self, ctx: ExecutionContext) -> list[str]:
        message = ctx.task.user_message
        files_map = dict(ctx.task.metadata.get("files_map", {}) or {})
        requested = self._extract_path(message)
        matched = self._lookup_source(files_map, requested)
        if matched:
            requested = str(matched.get("path") or requested or "")
        if not requested and files_map:
            requested = next(iter(files_map.keys()))
        payload = {"path": requested or "", "allowed": bool(requested)}
        return [self.put(ctx, "resolved_target", ArtifactType.STRUCTURED_JSON, payload)]

    def load_target_context(self, ctx: ExecutionContext) -> list[str]:
        files_map = dict(ctx.task.metadata.get("files_map", {}) or {})
        resolved = self.get(ctx, "resolved_target", {}) or {}
        path = str(resolved.get("path", ""))
        source = dict(self._lookup_source(files_map, path) or {})
        current = {
            "path": str(source.get("path", "")) or path,
            "content": str(source.get("content", "")),
            "content_hash": str(source.get("content_hash", "")),
        }
        return [self.put(ctx, "target_context", ArtifactType.STRUCTURED_JSON, current)]

    def plan_minimal_patch(self, ctx: ExecutionContext) -> list[str]:
        target = self.get(ctx, "target_context", {}) or {}
        plan = {
            "path": target.get("path", ""),
            "intent": "minimal_update",
            "instruction": ctx.task.user_message[:240],
        }
        return [self.put(ctx, "patch_plan", ArtifactType.STRUCTURED_JSON, plan)]

    def generate_patch(self, ctx: ExecutionContext) -> list[str]:
        target = self.get(ctx, "target_context", {}) or {}
        plan = self.get(ctx, "patch_plan", {}) or {}
        path = str(target.get("path", ""))
        base = str(target.get("content_hash", "") or "orchestrator-generated")
        original = str(target.get("content", ""))
        note = f"\n\n<!-- orchestrator note: {plan.get('instruction', '')[:100]} -->\n"
        proposed = (original + note).strip() if original else note.strip()
        changeset = [
            {
                "op": "update" if original else "create",
                "path": path,
                "base_hash": base if original else None,
                "proposed_content": proposed,
                "reason": "targeted file update",
                "hunks": [],
            }
        ]
        return [self.put(ctx, "raw_changeset", ArtifactType.CHANGESET, changeset)]

    def validate_patch_safety(self, ctx: ExecutionContext) -> list[str]:
        changeset = self.get(ctx, "raw_changeset", []) or []
        safe = len(changeset) == 1
        report = {"safe": safe, "items": len(changeset), "reason": "single-file patch expected"}
        return [self.put(ctx, "patch_validation_report", ArtifactType.STRUCTURED_JSON, report)]

    def finalize_changeset(self, ctx: ExecutionContext) -> list[str]:
        report = self.get(ctx, "patch_validation_report", {}) or {}
        if not report.get("safe"):
            return [self.put(ctx, "final_changeset", ArtifactType.CHANGESET, [])]
        changeset = self.get(ctx, "raw_changeset", []) or []
        return [self.put(ctx, "final_changeset", ArtifactType.CHANGESET, changeset)]

    def compose_edit_summary(self, ctx: ExecutionContext) -> list[str]:
        count = len(self.get(ctx, "final_changeset", []) or [])
        text = f"Prepared targeted edit changeset with {count} item(s)."
        return [self.put(ctx, "final_answer", ArtifactType.TEXT, text)]

    def _extract_path(self, text: str) -> str | None:
        match = re.search(r"\b[\w./-]+\.(md|txt|rst|yaml|yml|json|toml|ini|cfg)\b", text or "", flags=re.IGNORECASE)
        if not match:
            return None
        return match.group(0).replace("\\", "/").strip()

    def _lookup_source(self, files_map: dict[str, dict], path: str | None) -> dict | None:
        if not path:
            return None
        normalized = str(path).replace("\\", "/").strip()
        if not normalized:
            return None
        source = files_map.get(normalized)
        if source:
            return source
        normalized_low = normalized.lower()
        for key, value in files_map.items():
            if str(key).replace("\\", "/").lower() == normalized_low:
                return value
        return None
