from __future__ import annotations

import os
import threading
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Optional, Any, TypeVar, cast, TYPE_CHECKING, Generic, ClassVar, Callable, Type
from uuid import UUID

from .markers import Markers
from .style import NamedStyles, Style
from .utils import replace_if_changed

if TYPE_CHECKING:
    from rewrite import TreeVisitor, ExecutionContext
    from .markers import Marker
    from .parser import ParserInput
    from .visitor import Cursor

P = TypeVar('P')


class Tree(ABC):
    @property
    @abstractmethod
    def id(self) -> UUID:
        ...

    @property
    @abstractmethod
    def markers(self) -> Markers:
        ...

    @abstractmethod
    def is_acceptable(self, v: TreeVisitor[Any, P], p: P) -> bool:
        ...

    def accept(self, v: TreeVisitor[Any, P], p: P) -> Optional[Any]:
        return v.default_value(self, p)

    def print(self, cursor: 'Cursor', capture: 'PrintOutputCapture[P]') -> str:
        printer = self.printer(cursor)
        printer.set_cursor(cursor)
        printer.visit(self, capture)
        return capture.get_out()

    def printer(self, cursor: 'Cursor') -> 'TreeVisitor[Any, PrintOutputCapture[P]]':
        return cursor.first_enclosing_or_throw(SourceFile).printer(cursor)

    def is_scope(self, tree: Optional[Tree]) -> bool:
        return tree is not None and tree.id == self.id

    def __eq__(self, other: object) -> bool:
        if self.__class__ == other.__class__:
            return self.id == cast(Tree, other).id
        return False

    def __hash__(self) -> int:
        return hash(self.id)

    def replace(self, **kwargs) -> 'Tree':
        """Replace fields on this tree node, returning self if nothing changed."""
        return replace_if_changed(self, **kwargs)


class PrinterFactory(ABC):
    _thread_local = threading.local()

    @classmethod
    def current(cls) -> Optional[PrinterFactory]:
        return getattr(PrinterFactory._thread_local, 'context', None)

    def set_current(self):
        PrinterFactory._thread_local.context = self

    @abstractmethod
    def create_printer(self, cursor: Cursor) -> TreeVisitor[Any, PrintOutputCapture[P]]:
        ...


S = TypeVar('S', bound=Style)

class SourceFile(Tree):
    @property
    @abstractmethod
    def charset_name(self) -> Optional[str]:
        ...

    @property
    @abstractmethod
    def source_path(self) -> Path:
        ...

    @property
    @abstractmethod
    def file_attributes(self) -> Optional[FileAttributes]:
        ...

    def print_all(self) -> str:
        from .visitor import Cursor
        return self.print(Cursor(None, Cursor.ROOT_VALUE), PrintOutputCapture(0))

    def print_equals_input(self, input: 'ParserInput', ctx: ExecutionContext) -> bool:
        printed = self.print_all()
        return printed == input.source().read()

    def get_style(self, style: Type[S]) -> Optional[S]:
        return NamedStyles.merge(style, self.markers.find_all(NamedStyles))


@dataclass(frozen=True)
class FileAttributes:
    creation_time: Optional[datetime]
    last_modified_time: Optional[datetime]
    last_access_time: Optional[datetime]
    is_readable: bool
    is_writable: bool
    is_executable: bool
    size: int

    @staticmethod
    def from_path(path: Path) -> Optional[FileAttributes]:
        if path.exists():
            try:
                # Get file stats
                stat = path.stat()
                creation_time = datetime.fromtimestamp(stat.st_ctime)
                last_modified_time = datetime.fromtimestamp(stat.st_mtime)
                last_access_time = datetime.fromtimestamp(stat.st_atime)

                is_readable = os.access(path, os.R_OK)
                is_writable = os.access(path, os.W_OK)
                is_executable = os.access(path, os.X_OK)
                size = stat.st_size

                return FileAttributes(creation_time, last_access_time, last_modified_time, is_readable, is_writable,
                                      is_executable, size)
            except OSError:
                pass
        return None


@dataclass(frozen=True)
class Checksum:
    algorithm: str
    value: bytes


class PrintOutputCapture(Generic[P]):
    @dataclass
    class MarkerPrinter(ABC):
        DEFAULT: ClassVar[Optional['PrintOutputCapture.MarkerPrinter']] = None  # Set at module load
        SEARCH_MARKERS_ONLY: ClassVar[Optional['PrintOutputCapture.MarkerPrinter']] = None  # Set at module load
        VERBOSE: ClassVar[Optional['PrintOutputCapture.MarkerPrinter']] = None  # Set at module load
        FENCED: ClassVar[Optional['PrintOutputCapture.MarkerPrinter']] = None  # Set at module load
        SANITIZED: ClassVar[Optional['PrintOutputCapture.MarkerPrinter']] = None  # Set at module load

        def before_syntax(self, marker: 'Marker', cursor: 'Cursor', comment_wrapper: Callable[[str], str]) -> str:
            return ""

        def before_prefix(self, marker: 'Marker', cursor: 'Cursor', comment_wrapper: Callable[[str], str]) -> str:
            return ""

        def after_syntax(self, marker: 'Marker', cursor: 'Cursor', comment_wrapper: Callable[[str], str]) -> str:
            return ""

    def __init__(self, p: P, marker_printer: Optional['PrintOutputCapture.MarkerPrinter'] = None):
        self._context = p
        self._marker_printer: PrintOutputCapture.MarkerPrinter = marker_printer or PrintOutputCapture.MarkerPrinter.DEFAULT  # ty: ignore[invalid-assignment]  # DEFAULT always set at module load
        self._out: list[str] = []

    def get_out(self) -> str:
        return ''.join(self._out)

    def append(self, text: Optional[str] = None) -> 'PrintOutputCapture[P]':
        if text and len(text) > 0:
            self._out.append(text)
        return self

    def append_char(self, c: str) -> 'PrintOutputCapture[P]':
        if len(c) == 1:
            self._out.append(c)
        return self

    def clone(self) -> 'PrintOutputCapture[P]':
        return PrintOutputCapture(self._context, self._marker_printer)

    @property
    def marker_printer(self) -> MarkerPrinter:
        return self._marker_printer

    def get_marker_printer(self) -> MarkerPrinter:
        """Return the marker printer (alias for marker_printer property)."""
        return self._marker_printer


@dataclass
class _DefaultMarkerPrinter(PrintOutputCapture.MarkerPrinter):
    def before_syntax(self, marker: 'Marker', cursor: 'Cursor', comment_wrapper: Callable[[str], str]) -> str:
        return marker.print(cursor, comment_wrapper, False)


@dataclass
class _SearchMarkersOnlyPrinter(PrintOutputCapture.MarkerPrinter):
    """Only prints SearchResult markers, ignores all others."""
    def before_syntax(self, marker: 'Marker', cursor: 'Cursor', comment_wrapper: Callable[[str], str]) -> str:
        from .markers import SearchResult
        if isinstance(marker, SearchResult):
            return marker.print(cursor, comment_wrapper, False)
        return ""


@dataclass
class _VerboseMarkerPrinter(PrintOutputCapture.MarkerPrinter):
    """Prints all markers with verbose=True (shows detail instead of message)."""
    def before_syntax(self, marker: 'Marker', cursor: 'Cursor', comment_wrapper: Callable[[str], str]) -> str:
        return marker.print(cursor, comment_wrapper, True)


@dataclass
class _FencedMarkerPrinter(PrintOutputCapture.MarkerPrinter):
    """Prints SearchResult and Markup markers with fenced {{uuid}} format."""
    def before_syntax(self, marker: 'Marker', cursor: 'Cursor', comment_wrapper: Callable[[str], str]) -> str:
        from .markers import SearchResult, Markup  # ty: ignore[unresolved-import]
        if isinstance(marker, (SearchResult, Markup)):
            return "{{" + str(marker.id) + "}}"
        return ""

    def after_syntax(self, marker: 'Marker', cursor: 'Cursor', comment_wrapper: Callable[[str], str]) -> str:
        from .markers import SearchResult, Markup  # ty: ignore[unresolved-import]
        if isinstance(marker, (SearchResult, Markup)):
            return "{{" + str(marker.id) + "}}"
        return ""


@dataclass
class _SanitizedMarkerPrinter(PrintOutputCapture.MarkerPrinter):
    """Prints nothing - produces clean output without any marker annotations."""
    pass


PrintOutputCapture.MarkerPrinter.DEFAULT = _DefaultMarkerPrinter()
PrintOutputCapture.MarkerPrinter.SEARCH_MARKERS_ONLY = _SearchMarkersOnlyPrinter()
PrintOutputCapture.MarkerPrinter.VERBOSE = _VerboseMarkerPrinter()
PrintOutputCapture.MarkerPrinter.FENCED = _FencedMarkerPrinter()
PrintOutputCapture.MarkerPrinter.SANITIZED = _SanitizedMarkerPrinter()
