import re
from collections.abc import Callable
from difflib import unified_diff
from pathlib import Path

import yaml
from loguru import logger
from pydantic import BaseModel, ConfigDict
from rich import print  # noqa: A004
from rich.console import Console
from rich.panel import Panel
from rich.syntax import Syntax


class UpdateVersionAction(BaseModel):
    project_root: Path = Path(__file__).parents[4]
    files_to_update: dict[str, Callable]
    console: Console = Console()

    model_config = ConfigDict(arbitrary_types_allowed=True)

    def __init__(self, **data):
        files_to_update = {
            "pyproject.toml": self._update_pyproject_toml,
            "charts/huesoporro/values.yaml": self._update_values_yaml,
            "charts/huesoporro/Chart.yaml": self._update_chart_yaml,
        }
        super().__init__(**data, files_to_update=files_to_update)

    def _read_file(self, filepath: Path) -> str:
        """
        Read the contents of a file.

        Args:
            filepath (Path): Path to the file to read.

        Returns:
            str: File contents
        """
        with filepath.open("r") as f:
            return f.read()

    def _write_file(self, filepath: Path, content: str):
        """
        Write content to a file.

        Args:
            filepath (Path): Path to the file to write.
            content (str): Content to write to the file.
        """
        with filepath.open("w") as f:
            f.write(content)

    def _update_pyproject_toml(self, filepath: Path, new_version: str) -> str:
        """
        Update version in pyproject.toml.

        Args:
            filepath (Path): Path to pyproject.toml
            new_version (str): New version to set

        Returns:
            str: Updated file content
        """
        content = self._read_file(filepath)
        version_pattern = r'(version\s*=\s*)[\'"](.+?)[\'"]'
        return re.sub(version_pattern, rf'\1"{new_version}"', content)

    def _update_values_yaml(self, filepath: Path, new_version: str) -> str:
        """
        Update image tag in values.yaml.

        Args:
            filepath (Path): Path to values.yaml
            new_version (str): New version to set

        Returns:
            str: Updated file content
        """
        with filepath.open("r") as file:
            values = yaml.safe_load(file)

        # Assumes image.tag exists in the values.yaml
        values["image"]["tag"] = new_version

        return yaml.dump(values, default_flow_style=False)

    def _update_chart_yaml(self, filepath: Path, new_version: str) -> str:
        """
        Update version and appVersion in Chart.yaml.

        Args:
            filepath (Path): Path to Chart.yaml
            new_version (str): New version to set

        Returns:
            str: Updated file content
        """
        with filepath.open("r") as file:
            chart_data = yaml.safe_load(file)

        chart_data["version"] = new_version
        chart_data["appVersion"] = new_version

        return yaml.dump(chart_data, default_flow_style=False)

    def _generate_diff(self, original: str, updated: str, filename: str) -> str:
        """
        Generate a unified diff between original and updated content.

        Args:
            original (str): Original file content
            updated (str): Updated file content
            filename (str): Name of the file

        Returns:
            str: Unified diff representation
        """
        # Split content into lines
        original_lines = original.splitlines(keepends=True)
        updated_lines = updated.splitlines(keepends=True)

        # Generate unified diff
        diff_lines = list(
            unified_diff(
                original_lines,
                updated_lines,
                fromfile=f"a/{filename}",
                tofile=f"b/{filename}",
                lineterm="",
            )
        )

        return "\n".join(diff_lines)

    def _rich_display_diff(self, diff: str):
        """
        Display diff using rich for colorful output.

        Args:
            diff (str): Unified diff to display
        """
        if not diff:
            return

        # Use Syntax for syntax highlighting
        syntax = Syntax(diff, "diff", theme="ansi_dark")

        # Create a panel with the diff
        panel = Panel(
            syntax, title="Version Update Diff", border_style="cyan", expand=False
        )

        # Display the panel
        self.console.print(panel)

    def run(self, new_version: str, dry_run: bool = False):
        """
        Update version across all specified files.

        Args:
            new_version (str): New version to set
            dry_run (bool): Dry run mode with diff display
        """
        for relative_path, update_func in self.files_to_update.items():
            filepath = self.project_root / relative_path

            if not filepath.exists():
                logger.warning(f"Warning: {filepath} not found. Skipping.")
                continue

            try:
                # Read original content
                original_content = self._read_file(filepath)

                # Generate updated content
                updated_content = update_func(filepath, new_version)

                if dry_run:
                    # Generate and display diff
                    diff = self._generate_diff(
                        original_content, updated_content, str(relative_path)
                    )

                    # Display the diff
                    if diff:
                        print(f"\nDiff for {relative_path}:")
                        self._rich_display_diff(diff)
                else:
                    # Write updated content
                    self._write_file(filepath, updated_content)
                    print(f"Updated {relative_path}")

            except Exception as exc:  # noqa: BLE001
                logger.error(f"Error updating {relative_path}: {exc}")

        if dry_run:
            print("\nDry run complete. No files were modified.")
