"""Diff tool: compare texts and files, output structured differences."""

from __future__ import annotations

import difflib
import json
from pathlib import Path
from typing import Any

from loguru import logger

from madpod_agent.agent.tools.base import Tool

_MAX_INPUT_CHARS = 500_000


def _resolve_path(path: str, workspace: Path, allowed_dir: Path | None) -> Path:
    p = Path(path).expanduser()
    if not p.is_absolute():
        p = workspace / p
    resolved = p.resolve()
    if allowed_dir:
        try:
            resolved.relative_to(allowed_dir.resolve())
        except ValueError:
            raise PermissionError(f"Path {path} is outside allowed directory")
    return resolved


class DiffTool(Tool):
    """Compare two texts or files and show differences."""

    def __init__(self, workspace: Path, allowed_dir: Path | None = None):
        self._workspace = workspace
        self._allowed_dir = allowed_dir

    @property
    def name(self) -> str:
        return "diff"

    @property
    def description(self) -> str:
        return (
            "Compare two texts or files and show the differences. "
            "Supports file-to-file, text-to-text, or file-to-text comparison. "
            "Returns unified diff, summary statistics, and changed sections."
        )

    @property
    def parameters(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "text_a": {
                    "type": "string",
                    "description": "First text to compare (use this OR file_a)",
                },
                "text_b": {
                    "type": "string",
                    "description": "Second text to compare (use this OR file_b)",
                },
                "file_a": {
                    "type": "string",
                    "description": "Path to first file (relative to workspace)",
                },
                "file_b": {
                    "type": "string",
                    "description": "Path to second file (relative to workspace)",
                },
                "context_lines": {
                    "type": "integer",
                    "description": "Number of context lines around changes (default: 3)",
                },
                "ignore_whitespace": {
                    "type": "boolean",
                    "description": "Ignore whitespace differences (default: false)",
                },
            },
            "required": [],
        }

    async def execute(
        self,
        text_a: str | None = None,
        text_b: str | None = None,
        file_a: str | None = None,
        file_b: str | None = None,
        context_lines: int = 3,
        ignore_whitespace: bool = False,
        **kwargs: Any,
    ) -> str:
        # Resolve inputs
        label_a = "text_a"
        label_b = "text_b"

        if file_a:
            try:
                path_a = _resolve_path(file_a, self._workspace, self._allowed_dir)
                if not path_a.exists():
                    return json.dumps({"error": f"File not found: {file_a}"})
                text_a = path_a.read_text(encoding="utf-8", errors="replace")
                label_a = file_a
            except PermissionError as exc:
                return json.dumps({"error": str(exc)})

        if file_b:
            try:
                path_b = _resolve_path(file_b, self._workspace, self._allowed_dir)
                if not path_b.exists():
                    return json.dumps({"error": f"File not found: {file_b}"})
                text_b = path_b.read_text(encoding="utf-8", errors="replace")
                label_b = file_b
            except PermissionError as exc:
                return json.dumps({"error": str(exc)})

        if text_a is None or text_b is None:
            return json.dumps({"error": "Provide both inputs: (text_a + text_b) or (file_a + file_b)"})

        if len(text_a) > _MAX_INPUT_CHARS or len(text_b) > _MAX_INPUT_CHARS:
            return json.dumps({"error": f"Input too large (max {_MAX_INPUT_CHARS:,} chars each)"})

        lines_a = text_a.splitlines(keepends=True)
        lines_b = text_b.splitlines(keepends=True)

        if ignore_whitespace:
            stripped_a = [line.strip() for line in lines_a]
            stripped_b = [line.strip() for line in lines_b]
            diff_lines = list(difflib.unified_diff(
                stripped_a, stripped_b,
                fromfile=label_a, tofile=label_b,
                n=context_lines, lineterm="",
            ))
        else:
            diff_lines = list(difflib.unified_diff(
                lines_a, lines_b,
                fromfile=label_a, tofile=label_b,
                n=context_lines,
            ))

        # Statistics
        additions = sum(1 for l in diff_lines if l.startswith("+") and not l.startswith("+++"))
        deletions = sum(1 for l in diff_lines if l.startswith("-") and not l.startswith("---"))
        is_identical = len(diff_lines) == 0

        # Similarity ratio
        matcher = difflib.SequenceMatcher(None, lines_a, lines_b)
        similarity = round(matcher.ratio() * 100, 1)

        unified_diff = "".join(diff_lines)
        if len(unified_diff) > _MAX_INPUT_CHARS:
            unified_diff = unified_diff[:_MAX_INPUT_CHARS] + "\n... [truncated]"

        logger.info("Diff: {} vs {} — {}% similar, +{} -{}",
                     label_a, label_b, similarity, additions, deletions)

        return json.dumps({
            "ok": True,
            "identical": is_identical,
            "similarity_percent": similarity,
            "additions": additions,
            "deletions": deletions,
            "lines_a": len(lines_a),
            "lines_b": len(lines_b),
            "diff": unified_diff if not is_identical else "",
        })
