"""Remove file date prefix and replace with creation/modification date."""

from __future__ import annotations

import argparse
import concurrent.futures
import logging
import platform
import re
import time
from dataclasses import dataclass
from functools import cached_property, lru_cache
from pathlib import Path
from re import Pattern
from typing import TYPE_CHECKING, Final

logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger(__name__)

if TYPE_CHECKING:
    import os

# Configuration constants
DETECT_SEPARATORS: Final[str] = "-_#.~"
SEP: Final[str] = "_"
MAX_RETRY: Final[int] = 100
WINDOWS_RESERVED_NAME_LEN = 4


def _is_windows_reserved_name(filename: str) -> bool:
    """Check if the given filename is a Windows reserved name.

    Windows reserved names include CON, PRN, AUX, NUL, and COM1-COM9, LPT1-LPT9.
    These names cannot be used as file or directory names on Windows systems.

    Returns
    -------
        bool: True if the filename is a Windows reserved name, False otherwise.
    """
    if platform.system().lower() != "windows":
        return False

    # Normalize the filename to uppercase and strip extensions
    name = Path(filename).stem.upper()

    # Check for basic reserved names
    if name in {"CON", "PRN", "AUX", "NUL"}:
        return True

    # Check for COM and LPT device names (COM1-COM9, LPT1-LPT9)
    if (name.startswith(("COM", "LPT"))) and len(name) == WINDOWS_RESERVED_NAME_LEN:
        try:
            num = int(name[3])
            if 1 <= num <= 9:
                return True
        except ValueError:
            pass

    return False


# Date pattern for detection
DATE_PATTERN: Final[Pattern[str]] = re.compile(
    r"(20|19)\d{2}((0[1-9])|(1[012]))((0[1-9])|([12]\d)|(3[01]))",
)


@dataclass(frozen=True)
class SingleFileRenamer:
    """Renamer for single file."""

    path: Path

    @staticmethod
    def rename(path: Path) -> None:
        """Rename file with date prefix to creation/modification date."""
        renamer = SingleFileRenamer(path)

        dest_path = renamer.filepath_renamed
        sequence = 1
        while dest_path.exists() and sequence <= MAX_RETRY:
            logger.warning(f"{dest_path} already exists, adding unique suffix.")
            dest_path = renamer.filepath_renamed.with_name(
                f"{renamer.filestem_renamed}({sequence}){renamer.file_suffix}",
            )
            sequence += 1

        # If we've reached the max retry limit and the path still exists, give up
        if dest_path.exists() and sequence > MAX_RETRY:
            logger.error(f"Max retry reached, giving up on {renamer.path}.")
            return

        try:
            renamer.path.rename(dest_path)
        except Exception:
            logger.exception(f"Rename failed: {renamer.path} -> {dest_path}")
        else:
            logger.info(f"Rename: {renamer.path} -> {dest_path}")

    @cached_property
    def file_suffix(self) -> str:
        """Get file suffix."""
        return self.path.suffix

    @cached_property
    def filepath_renamed(self) -> Path:
        """Get renamed filepath."""
        return self.path.with_name(self.filename_renamed)

    @cached_property
    def filestem_renamed(self) -> str:
        """Get renamed file stem."""
        # Extract stem from the filename string
        suffix = self.path.suffix
        return (
            self.filename_renamed[: -len(suffix)]
            if suffix and self.filename_renamed.endswith(suffix)
            else self.filename_renamed
        )

    @cached_property
    def filename_renamed(self) -> str:
        """Get renamed filename."""
        return f"{self.time_mark}{SEP}{self.filestem_without_date}{self.path.suffix}"

    @cached_property
    def filestem_without_date(self) -> str:
        """Get file stem without date prefix."""

        @lru_cache(maxsize=1024)
        def remove_date_prefix(filestem: str) -> str:
            """Remove date prefix from filename.

            Returns
            -------
                str: The filename with the date prefix removed.
            """
            if (filestem.startswith(".") and not filestem[1:].isdigit() and len(filestem) > 1) and not re.search(
                DATE_PATTERN, filestem
            ):
                return ""

            match = re.search(DATE_PATTERN, filestem)
            if not match:
                logger.debug(f"No date prefix found: {filestem}")
                return filestem
            b, e = match.start(), match.end()
            if b >= 1 and filestem[b - 1] in DETECT_SEPARATORS:
                filestem = filestem[: b - 1] + filestem[e:]
            elif e < len(filestem) and filestem[e] in DETECT_SEPARATORS:
                filestem = filestem[:b] + filestem[e + 1 :]
            return remove_date_prefix(filestem)

        return remove_date_prefix(self.filestem)

    @cached_property
    def filestem(self) -> str:
        """Get current file stem."""
        return self.path.stem

    @cached_property
    def filestat(self) -> os.stat_result:
        """Get file stat."""
        return self.path.stat()

    @cached_property
    def modified_time(self) -> float:
        """Get modified time."""
        return self.filestat.st_mtime

    @cached_property
    def created_time(self) -> float:
        """Get created time."""
        return self.filestat.st_ctime

    @cached_property
    def time_mark(self) -> str:
        """Get time mark."""
        return time.strftime(
            "%Y%m%d",
            time.localtime(max((self.modified_time, self.created_time))),
        )


@dataclass(frozen=True)
class MultiFileRenamer:
    """Renamer for multiple files."""

    paths: list[str]

    @staticmethod
    def rename_files(paths: list[str]) -> None:
        """Rename files with date prefix to creation/modification date."""
        renamer = MultiFileRenamer(paths)
        if not renamer.filtered_paths:
            logger.error("No valid files to process.")
            return

        t0 = time.perf_counter()
        with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
            executor.map(SingleFileRenamer.rename, renamer.filtered_paths)
        logger.info(f"Done in {time.perf_counter() - t0:.4f}s")

    @cached_property
    def converted_paths(self) -> list[Path]:
        """Get filtered paths."""
        return [Path(p) for p in self.paths]

    @cached_property
    def filtered_paths(self) -> list[Path]:
        """Get filtered paths."""
        return [p for p in self.converted_paths if p.exists() and not _is_windows_reserved_name(p.name)]

    @cached_property
    def missing_paths(self) -> list[Path]:
        """Get missing paths."""
        return list(set(self.converted_paths) - set(self.filtered_paths))


def main() -> None:
    """Run entry point for the command-line interface."""
    parser = argparse.ArgumentParser(
        prog="filedate",
        description="Remove file date prefix and replace with creation/modification date.",
    )
    parser.add_argument("targets", type=str, nargs="+", help="List of input files")
    parser.add_argument("--debug", "-d", action="store_true", help="Enable debug mode")
    args = parser.parse_args()

    if args.debug:
        logger.setLevel(logging.DEBUG)

    MultiFileRenamer.rename_files(args.targets)
