"""Input parsing for policy evaluation."""

from __future__ import annotations

import re
from collections.abc import Iterator
from dataclasses import dataclass, field
from enum import Enum


class LineType(Enum):
    """Type of line in a diff."""

    CONTEXT = "context"
    ADDITION = "addition"
    DELETION = "deletion"
    HEADER = "header"


@dataclass
class DiffLine:
    """A single line in a diff."""

    content: str
    line_type: LineType
    old_line_no: int | None = None
    new_line_no: int | None = None


@dataclass
class DiffHunk:
    """A hunk (section) of a diff."""

    old_start: int
    old_count: int
    new_start: int
    new_count: int
    lines: list[DiffLine] = field(default_factory=list)


@dataclass
class DiffFile:
    """A single file in a diff."""

    old_path: str | None
    new_path: str | None
    hunks: list[DiffHunk] = field(default_factory=list)
    is_new: bool = False
    is_deleted: bool = False
    is_binary: bool = False

    @property
    def path(self) -> str:
        """Get the current path of the file."""
        return self.new_path or self.old_path or ""

    def added_lines(self) -> Iterator[tuple[int, str]]:
        """Iterate over added lines with their line numbers."""
        for hunk in self.hunks:
            for line in hunk.lines:
                if line.line_type == LineType.ADDITION and line.new_line_no:
                    yield line.new_line_no, line.content


@dataclass
class FileLine:
    """A line in a file."""

    line_no: int
    content: str


@dataclass
class ParsedFile:
    """A parsed file for evaluation."""

    path: str
    content: str
    lines: list[FileLine] = field(default_factory=list)

    def __post_init__(self) -> None:
        if not self.lines and self.content:
            self.lines = [
                FileLine(line_no=i + 1, content=line)
                for i, line in enumerate(self.content.split("\n"))
            ]


class DiffParser:
    """Parser for unified diff format."""

    # Regex patterns for diff parsing
    FILE_HEADER_PATTERN = re.compile(r"^diff --git a/(.*) b/(.*)$")
    OLD_FILE_PATTERN = re.compile(r"^--- (?:a/)?(.*)$")
    NEW_FILE_PATTERN = re.compile(r"^\+\+\+ (?:b/)?(.*)$")
    HUNK_PATTERN = re.compile(r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@")
    BINARY_PATTERN = re.compile(r"^Binary files .* and .* differ$")
    NEW_FILE_MODE_PATTERN = re.compile(r"^new file mode")
    DELETED_FILE_MODE_PATTERN = re.compile(r"^deleted file mode")

    def parse(self, diff_text: str) -> list[DiffFile]:
        """Parse a unified diff into structured DiffFile objects."""
        files: list[DiffFile] = []
        current_file: DiffFile | None = None
        current_hunk: DiffHunk | None = None
        old_line = 0
        new_line = 0

        for line in diff_text.split("\n"):
            # Check for file header
            file_match = self.FILE_HEADER_PATTERN.match(line)
            if file_match:
                if current_file:
                    files.append(current_file)
                current_file = DiffFile(
                    old_path=file_match.group(1),
                    new_path=file_match.group(2),
                )
                current_hunk = None
                continue

            if not current_file:
                continue

            # Check for new/deleted file mode
            if self.NEW_FILE_MODE_PATTERN.match(line):
                current_file.is_new = True
                continue
            if self.DELETED_FILE_MODE_PATTERN.match(line):
                current_file.is_deleted = True
                continue

            # Check for binary file
            if self.BINARY_PATTERN.match(line):
                current_file.is_binary = True
                continue

            # Check for old file path
            old_match = self.OLD_FILE_PATTERN.match(line)
            if old_match:
                path = old_match.group(1)
                if path != "/dev/null":
                    current_file.old_path = path
                continue

            # Check for new file path
            new_match = self.NEW_FILE_PATTERN.match(line)
            if new_match:
                path = new_match.group(1)
                if path != "/dev/null":
                    current_file.new_path = path
                continue

            # Check for hunk header
            hunk_match = self.HUNK_PATTERN.match(line)
            if hunk_match:
                current_hunk = DiffHunk(
                    old_start=int(hunk_match.group(1)),
                    old_count=int(hunk_match.group(2) or 1),
                    new_start=int(hunk_match.group(3)),
                    new_count=int(hunk_match.group(4) or 1),
                )
                current_file.hunks.append(current_hunk)
                old_line = current_hunk.old_start
                new_line = current_hunk.new_start
                continue

            if not current_hunk:
                continue

            # Parse diff lines
            if line.startswith("+"):
                current_hunk.lines.append(
                    DiffLine(
                        content=line[1:],
                        line_type=LineType.ADDITION,
                        new_line_no=new_line,
                    )
                )
                new_line += 1
            elif line.startswith("-"):
                current_hunk.lines.append(
                    DiffLine(
                        content=line[1:],
                        line_type=LineType.DELETION,
                        old_line_no=old_line,
                    )
                )
                old_line += 1
            elif line.startswith(" "):
                current_hunk.lines.append(
                    DiffLine(
                        content=line[1:],
                        line_type=LineType.CONTEXT,
                        old_line_no=old_line,
                        new_line_no=new_line,
                    )
                )
                old_line += 1
                new_line += 1

        if current_file:
            files.append(current_file)

        return files


class FileParser:
    """Parser for file content."""

    def parse(self, content: str, path: str = "") -> ParsedFile:
        """Parse file content into a ParsedFile object."""
        return ParsedFile(path=path, content=content)

    def parse_files(self, files: dict[str, str]) -> list[ParsedFile]:
        """Parse multiple files."""
        return [self.parse(content, path) for path, content in files.items()]
