"""
File References - Replace @filename with file contents

Supports:
- @filename - Exact file path
- @partial - Fuzzy search for matching files
- Multiple file references in single template

Examples:
    expand_files("Review @app.py", project_path)
    → "Review <contents of app.py>"

    expand_files("Compare @src/old.py and @src/new.py", project_path)
    → "Compare <contents of old.py> and <contents of new.py>"
"""

import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional


@dataclass
class FileReference:
    """
    Represents a file reference found in template

    Attributes:
        original: Original @filename text
        filename: Extracted filename
        resolved_path: Resolved absolute path (if found)
        content: File content (if loaded)
        error: Error message (if any)
    """

    original: str
    filename: str
    resolved_path: Optional[Path] = None
    content: Optional[str] = None
    error: Optional[str] = None

    @property
    def found(self) -> bool:
        """Whether file was found"""
        return self.resolved_path is not None

    @property
    def loaded(self) -> bool:
        """Whether content was loaded"""
        return self.content is not None


@dataclass
class FileExpansionResult:
    """
    Result of file reference expansion

    Attributes:
        output: Template with file references replaced
        success: Whether all files were found and loaded
        references: List of file references processed
        warnings: List of warning messages
    """

    output: str
    success: bool = True
    references: list[FileReference] = field(default_factory=list)
    warnings: list[str] = field(default_factory=list)


class FileExpander:
    """
    Expand @filename references in templates

    Supports:
    - Exact file paths: @src/app.py
    - Relative paths: @./config.json
    - Fuzzy matching: @app (finds app.py, app.ts, etc.)

    Example:
        expander = FileExpander(project_path=Path.cwd())
        result = expander.expand("Review @app.py code")
        print(result.output)
    """

    # Pattern to match @filename (filename can include path separators)
    # Matches: @file.py, @src/file.py, @./file.py, @"file with spaces.py"
    FILE_REF_PATTERN = re.compile(r'@(?:"([^"]+)"|\'([^\']+)\'|([^\s@"\']+))')

    def __init__(
        self,
        project_path: Optional[Path] = None,
        max_file_size: int = 1024 * 1024,  # 1MB default
    ):
        """
        Initialize FileExpander

        Args:
            project_path: Project root for relative paths (defaults to cwd)
            max_file_size: Maximum file size to read (bytes)
        """
        self.project_path = project_path or Path.cwd()
        self.max_file_size = max_file_size

    def expand(
        self,
        template: str,
        strict: bool = False,
    ) -> FileExpansionResult:
        """
        Expand file references in template

        Args:
            template: Template with @filename references
            strict: If True, fail on missing files

        Returns:
            FileExpansionResult with expanded output
        """
        references: list[FileReference] = []
        warnings: list[str] = []
        success = True

        # Find all file references
        matches = list(self.FILE_REF_PATTERN.finditer(template))

        if not matches:
            return FileExpansionResult(output=template, success=True)

        # Process each reference (reverse order to preserve indices)
        output = template
        for match in reversed(matches):
            # Extract filename from match groups (quoted or unquoted)
            filename = match.group(1) or match.group(2) or match.group(3)
            original = match.group(0)

            ref = self._resolve_and_load(filename, original)
            references.insert(0, ref)  # Maintain order

            if ref.error:
                warnings.append(ref.error)
                success = False

            # Replace in output
            if ref.loaded:
                replacement = ref.content
            elif strict:
                replacement = original  # Keep original on error in strict mode
            else:
                replacement = f"[File not found: {filename}]"

            output = output[: match.start()] + replacement + output[match.end() :]

        return FileExpansionResult(
            output=output,
            success=success,
            references=references,
            warnings=warnings,
        )

    def _resolve_and_load(self, filename: str, original: str) -> FileReference:
        """
        Resolve and load a file

        Args:
            filename: Filename to resolve
            original: Original @filename text

        Returns:
            FileReference with resolved path and content
        """
        ref = FileReference(original=original, filename=filename)

        # Try to resolve the path
        resolved = self._resolve_path(filename)

        if resolved is None:
            # Try fuzzy matching
            resolved = self._fuzzy_find(filename)

        if resolved is None:
            ref.error = f"File not found: {filename}"
            return ref

        ref.resolved_path = resolved

        # Load content
        try:
            if resolved.stat().st_size > self.max_file_size:
                ref.error = f"File too large: {filename} ({resolved.stat().st_size} bytes)"
                return ref

            ref.content = resolved.read_text(encoding="utf-8")
        except UnicodeDecodeError:
            ref.error = f"Cannot read binary file: {filename}"
        except PermissionError:
            ref.error = f"Permission denied: {filename}"
        except Exception as e:
            ref.error = f"Error reading {filename}: {e}"

        return ref

    def _resolve_path(self, filename: str) -> Optional[Path]:
        """
        Resolve filename to absolute path

        Args:
            filename: Filename (may be relative or absolute)

        Returns:
            Absolute Path if file exists, None otherwise
        """
        # Handle absolute paths
        path = Path(filename)
        if path.is_absolute():
            return path if path.exists() and path.is_file() else None

        # Try relative to project path
        project_relative = self.project_path / filename
        if project_relative.exists() and project_relative.is_file():
            return project_relative.resolve()

        # Try relative to cwd
        cwd_relative = Path.cwd() / filename
        if cwd_relative.exists() and cwd_relative.is_file():
            return cwd_relative.resolve()

        return None

    def _fuzzy_find(self, filename: str) -> Optional[Path]:
        """
        Fuzzy find a file by name

        Searches for files matching the filename pattern in project.

        Args:
            filename: Filename to search for

        Returns:
            Path if found, None otherwise
        """
        # Extract just the filename without directory
        search_name = Path(filename).name

        # Common extensions to try if no extension provided
        extensions = ["", ".py", ".ts", ".js", ".md", ".json", ".yaml", ".yml", ".txt"]

        # Try exact name first, then with extensions
        for ext in extensions:
            search_pattern = search_name + ext if not search_name.endswith(ext) else search_name

            # Search in project directory (limited depth for performance)
            for match in self.project_path.rglob(search_pattern):
                if match.is_file():
                    return match

        return None

    def find_references(self, template: str) -> list[str]:
        """
        Find all file references in template without expanding

        Args:
            template: Template to search

        Returns:
            List of filenames referenced
        """
        filenames = []
        for match in self.FILE_REF_PATTERN.finditer(template):
            filename = match.group(1) or match.group(2) or match.group(3)
            filenames.append(filename)
        return filenames

    def has_references(self, template: str) -> bool:
        """
        Check if template has any file references

        Args:
            template: Template to check

        Returns:
            True if template contains @filename references
        """
        return bool(self.FILE_REF_PATTERN.search(template))


# Convenience functions


def expand_files(
    template: str,
    project_path: Optional[Path] = None,
    strict: bool = False,
) -> str:
    """
    Expand file references in template (convenience function)

    Args:
        template: Template with @filename references
        project_path: Project root directory
        strict: If True, keep @filename on missing files

    Returns:
        Template with file contents substituted
    """
    expander = FileExpander(project_path=project_path)
    result = expander.expand(template, strict=strict)
    return result.output


def expand_files_with_result(
    template: str,
    project_path: Optional[Path] = None,
    strict: bool = False,
) -> FileExpansionResult:
    """
    Expand file references and return full result

    Args:
        template: Template with @filename references
        project_path: Project root directory
        strict: If True, keep @filename on missing files

    Returns:
        FileExpansionResult with output and metadata
    """
    expander = FileExpander(project_path=project_path)
    return expander.expand(template, strict=strict)
