import json
import re
from difflib import SequenceMatcher

from app.modules.agent.engine.graphs.file_targeting import FileTargeting
from app.modules.agent.engine.graphs.state import AgentGraphState
from app.schemas.changeset import ChangeItem


class ProjectEditsSupport:
    def __init__(self, max_context_files: int = 12, max_preview_chars: int = 2500) -> None:
        self._max_context_files = max_context_files
        self._max_preview_chars = max_preview_chars
        self._targeting = FileTargeting()

    def pick_relevant_files(self, message: str, files_map: dict[str, dict]) -> list[dict]:
        tokens = {x for x in (message or "").lower().replace("/", " ").split() if len(x) >= 4}
        scored: list[tuple[int, dict]] = []
        for path, payload in files_map.items():
            content = str(payload.get("content", ""))
            score = 0
            low_path = path.lower()
            low_content = content.lower()
            for token in tokens:
                if token in low_path:
                    score += 3
                if token in low_content:
                    score += 1
            scored.append((score, self.as_candidate(payload)))
        scored.sort(key=lambda x: (-x[0], x[1]["path"]))
        return [item for _, item in scored[: self._max_context_files]]

    def as_candidate(self, payload: dict) -> dict:
        return {
            "path": str(payload.get("path", "")).replace("\\", "/"),
            "content": str(payload.get("content", "")),
            "content_hash": str(payload.get("content_hash", "")),
        }

    def normalize_file_output(self, text: str) -> str:
        value = (text or "").strip()
        if value.startswith("```") and value.endswith("```"):
            lines = value.splitlines()
            if len(lines) >= 3:
                return "\n".join(lines[1:-1]).strip()
        return value

    def parse_json(self, raw: str):
        text = self.normalize_file_output(raw)
        try:
            return json.loads(text)
        except Exception:
            return {}

    def shorten(self, text: str, max_chars: int | None = None) -> str:
        limit = max_chars or self._max_preview_chars
        value = (text or "").strip()
        if len(value) <= limit:
            return value
        return value[:limit].rstrip() + "\n...[truncated]"

    def collapse_whitespace(self, text: str) -> str:
        return re.sub(r"\s+", " ", (text or "").strip())

    def similarity(self, original: str, updated: str) -> float:
        return SequenceMatcher(None, original or "", updated or "").ratio()

    def line_change_ratio(self, original: str, updated: str) -> float:
        orig_lines = (original or "").splitlines()
        new_lines = (updated or "").splitlines()
        if not orig_lines and not new_lines:
            return 0.0
        matcher = SequenceMatcher(None, orig_lines, new_lines)
        changed = 0
        for tag, i1, i2, j1, j2 in matcher.get_opcodes():
            if tag == "equal":
                continue
            changed += max(i2 - i1, j2 - j1)
        total = max(len(orig_lines), len(new_lines), 1)
        return changed / total

    def added_headings(self, original: str, updated: str) -> int:
        old_heads = {line.strip() for line in (original or "").splitlines() if line.strip().startswith("#")}
        new_heads = {line.strip() for line in (updated or "").splitlines() if line.strip().startswith("#")}
        return len(new_heads - old_heads)

    def build_summary(self, state: AgentGraphState, changeset: list[ChangeItem]) -> str:
        if not changeset:
            return "Правки не сформированы: changeset пуст."
        lines = [
            "Выполненные действия:",
            f"- Проанализирован запрос: {state.get('message', '')}",
            "- Сформирован контракт правок с разрешенными блоками изменений.",
            f"- Проведен self-check: {state.get('validation_feedback', 'без замечаний')}",
            "",
            "Измененные файлы:",
        ]
        for item in changeset[:30]:
            lines.append(f"- {item.op.value} {item.path}: {item.reason}")
        return "\n".join(lines)

    def is_broad_rewrite_request(self, message: str) -> bool:
        low = (message or "").lower()
        markers = (
            "перепиши",
            "полностью",
            "целиком",
            "с нуля",
            "full rewrite",
            "rewrite all",
            "реорганизуй документ",
        )
        return any(marker in low for marker in markers)

    def lookup_file(self, files_map: dict[str, dict], path: str) -> dict | None:
        return self._targeting.lookup_file(files_map, path)
