"""Data models for file-organizer.

This module defines the core data structures used throughout the application:
- OrganizeMode: Enum for organization strategies (type, date, size)
- FileCategory: Enum for file type categories (Images, Documents, etc.)
- EXTENSION_CATEGORIES: Mapping from file extension to FileCategory
- FileAction: Single file move operation
- OrganizePlan: Collection of actions for a directory
- UndoLog: Record of an executed plan for reversal
"""

from __future__ import annotations

import json
import time
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any


class OrganizeMode(str, Enum):
    """Organization strategy modes.

    TYPE: Organize by file extension/category (Images, Documents, etc.)
    DATE: Organize by file modification date (year-month folders)
    SIZE: Organize by file size (Small/Medium/Large folders)
    """

    TYPE = "type"
    DATE = "date"
    SIZE = "size"


class FileCategory(str, Enum):
    """File categories for type-based organization.

    Each category maps to a folder name. Files with unknown extensions
    go to OTHER.
    """

    IMAGES = "Images"
    DOCUMENTS = "Documents"
    VIDEOS = "Videos"
    AUDIO = "Audio"
    ARCHIVES = "Archives"
    CODE = "Code"
    EXECUTABLES = "Executables"
    SPREADSHEETS = "Spreadsheets"
    PRESENTATIONS = "Presentations"
    FONTS = "Fonts"
    DATA = "Data"
    OTHER = "Other"


# Extension to category mapping
# Maps lowercase file extensions (including dot) to FileCategory enum values.
# Used by FileOrganizer.get_category() to determine destination folder.
EXTENSION_CATEGORIES: dict[str, FileCategory] = {
    # Images
    ".jpg": FileCategory.IMAGES,
    ".jpeg": FileCategory.IMAGES,
    ".png": FileCategory.IMAGES,
    ".gif": FileCategory.IMAGES,
    ".bmp": FileCategory.IMAGES,
    ".webp": FileCategory.IMAGES,
    ".svg": FileCategory.IMAGES,
    ".tiff": FileCategory.IMAGES,
    ".ico": FileCategory.IMAGES,
    ".heic": FileCategory.IMAGES,
    ".avif": FileCategory.IMAGES,
    # Documents
    ".pdf": FileCategory.DOCUMENTS,
    ".doc": FileCategory.DOCUMENTS,
    ".docx": FileCategory.DOCUMENTS,
    ".txt": FileCategory.DOCUMENTS,
    ".rtf": FileCategory.DOCUMENTS,
    ".odt": FileCategory.DOCUMENTS,
    ".tex": FileCategory.DOCUMENTS,
    ".md": FileCategory.DOCUMENTS,
    # Videos
    ".mp4": FileCategory.VIDEOS,
    ".mkv": FileCategory.VIDEOS,
    ".avi": FileCategory.VIDEOS,
    ".mov": FileCategory.VIDEOS,
    ".wmv": FileCategory.VIDEOS,
    ".flv": FileCategory.VIDEOS,
    ".webm": FileCategory.VIDEOS,
    ".m4v": FileCategory.VIDEOS,
    # Audio
    ".mp3": FileCategory.AUDIO,
    ".wav": FileCategory.AUDIO,
    ".flac": FileCategory.AUDIO,
    ".aac": FileCategory.AUDIO,
    ".ogg": FileCategory.AUDIO,
    ".m4a": FileCategory.AUDIO,
    ".wma": FileCategory.AUDIO,
    # Archives
    ".zip": FileCategory.ARCHIVES,
    ".rar": FileCategory.ARCHIVES,
    ".7z": FileCategory.ARCHIVES,
    ".tar": FileCategory.ARCHIVES,
    ".gz": FileCategory.ARCHIVES,
    ".bz2": FileCategory.ARCHIVES,
    ".xz": FileCategory.ARCHIVES,
    # Code
    ".py": FileCategory.CODE,
    ".js": FileCategory.CODE,
    ".ts": FileCategory.CODE,
    ".jsx": FileCategory.CODE,
    ".tsx": FileCategory.CODE,
    ".html": FileCategory.CODE,
    ".css": FileCategory.CODE,
    ".scss": FileCategory.CODE,
    ".json": FileCategory.CODE,
    ".xml": FileCategory.CODE,
    ".yaml": FileCategory.CODE,
    ".yml": FileCategory.CODE,
    ".toml": FileCategory.CODE,
    ".ini": FileCategory.CODE,
    ".cfg": FileCategory.CODE,
    ".conf": FileCategory.CODE,
    ".sh": FileCategory.CODE,
    ".bash": FileCategory.CODE,
    ".zsh": FileCategory.CODE,
    ".fish": FileCategory.CODE,
    ".ps1": FileCategory.CODE,
    ".bat": FileCategory.CODE,
    ".cmd": FileCategory.CODE,
    ".sql": FileCategory.CODE,
    ".r": FileCategory.CODE,
    ".jl": FileCategory.CODE,
    ".go": FileCategory.CODE,
    ".rs": FileCategory.CODE,
    ".cpp": FileCategory.CODE,
    ".c": FileCategory.CODE,
    ".h": FileCategory.CODE,
    ".hpp": FileCategory.CODE,
    ".java": FileCategory.CODE,
    ".kt": FileCategory.CODE,
    ".swift": FileCategory.CODE,
    ".php": FileCategory.CODE,
    ".rb": FileCategory.CODE,
    ".pl": FileCategory.CODE,
    ".lua": FileCategory.CODE,
    ".dart": FileCategory.CODE,
    # Executables
    ".exe": FileCategory.EXECUTABLES,
    ".msi": FileCategory.EXECUTABLES,
    ".dmg": FileCategory.EXECUTABLES,
    ".pkg": FileCategory.EXECUTABLES,
    ".deb": FileCategory.EXECUTABLES,
    ".rpm": FileCategory.EXECUTABLES,
    ".apk": FileCategory.EXECUTABLES,
    ".appimage": FileCategory.EXECUTABLES,
    # Spreadsheets
    ".xls": FileCategory.SPREADSHEETS,
    ".xlsx": FileCategory.SPREADSHEETS,
    ".csv": FileCategory.SPREADSHEETS,
    ".ods": FileCategory.SPREADSHEETS,
    ".numbers": FileCategory.SPREADSHEETS,
    # Presentations
    ".ppt": FileCategory.PRESENTATIONS,
    ".pptx": FileCategory.PRESENTATIONS,
    ".odp": FileCategory.PRESENTATIONS,
    ".key": FileCategory.PRESENTATIONS,
    # Fonts
    ".ttf": FileCategory.FONTS,
    ".otf": FileCategory.FONTS,
    ".woff": FileCategory.FONTS,
    ".woff2": FileCategory.FONTS,
    # Data
    ".db": FileCategory.DATA,
    ".sqlite": FileCategory.DATA,
    ".sqlite3": FileCategory.DATA,
    ".parquet": FileCategory.DATA,
    ".feather": FileCategory.DATA,
    ".h5": FileCategory.DATA,
    ".hdf5": FileCategory.DATA,
}


@dataclass
class FileAction:
    """Represents a single file move action.

    Contains source path, destination path, file size, and timestamp.
    Used by OrganizePlan to track what files will be moved where.
    """

    source: Path
    destination: Path
    size_bytes: int
    timestamp: float = field(default_factory=time.time)

    def to_dict(self) -> dict[str, Any]:
        """Serialize to dictionary for JSON storage.

        Returns:
            Dict with string paths, size in bytes, and timestamp.
        """
        return {
            "source": str(self.source),
            "destination": str(self.destination),
            "size_bytes": self.size_bytes,
            "timestamp": self.timestamp,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> FileAction:
        """Deserialize from dictionary.

        Args:
            data: Dict with source, destination, size_bytes, timestamp keys.

        Returns:
            New FileAction instance.
        """
        return cls(
            source=Path(data["source"]),
            destination=Path(data["destination"]),
            size_bytes=data["size_bytes"],
            timestamp=data.get("timestamp", time.time()),
        )


@dataclass
class OrganizePlan:
    """Plan for organizing a directory.

    Contains the organization mode, list of FileActions, source directory,
    creation timestamp, and dry_run flag. Used to preview and execute moves.
    """

    mode: OrganizeMode
    actions: list[FileAction] = field(default_factory=list)
    source_dir: Path = Path(".")
    timestamp: float = field(default_factory=time.time)
    dry_run: bool = True

    def add_action(self, source: Path, destination: Path, size_bytes: int) -> None:
        """Add a file move action to the plan.

        Args:
            source: Source file path.
            destination: Destination file path.
            size_bytes: File size in bytes.
        """
        self.actions.append(FileAction(source, destination, size_bytes))

    def total_files(self) -> int:
        """Return number of files in the plan."""
        return len(self.actions)

    def total_size(self) -> int:
        """Return total size of all files in bytes."""
        return sum(a.size_bytes for a in self.actions)

    def to_dict(self) -> dict[str, Any]:
        """Serialize to dictionary for JSON export/storage.

        Returns:
            Dict representation of the plan.
        """
        return {
            "mode": self.mode.value,
            "source_dir": str(self.source_dir),
            "timestamp": self.timestamp,
            "dry_run": self.dry_run,
            "actions": [a.to_dict() for a in self.actions],
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> OrganizePlan:
        """Deserialize from dictionary.

        Args:
            data: Dict with mode, source_dir, timestamp, dry_run, actions keys.

        Returns:
            New OrganizePlan instance with reconstructed actions.
        """
        plan = cls(
            mode=OrganizeMode(data["mode"]),
            source_dir=Path(data["source_dir"]),
            timestamp=data.get("timestamp", time.time()),
            dry_run=data.get("dry_run", True),
        )
        plan.actions = [FileAction.from_dict(a) for a in data.get("actions", [])]
        return plan


@dataclass
class UndoLog:
    """Log of an organize operation for undo functionality.

    Stores the original plan, completion timestamp, success status,
    and any error message. Saved as JSON to enable undo after restart.
    """

    plan: OrganizePlan
    completed_at: float = field(default_factory=time.time)
    success: bool = True
    error: str | None = None

    def to_dict(self) -> dict[str, Any]:
        """Serialize to dictionary for JSON storage.

        Returns:
            Dict with plan, completed_at, success, error.
        """
        return {
            "plan": self.plan.to_dict(),
            "completed_at": self.completed_at,
            "success": self.success,
            "error": self.error,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> UndoLog:
        """Deserialize from dictionary.

        Args:
            data: Dict with plan, completed_at, success, error keys.

        Returns:
            New UndoLog instance with reconstructed plan.
        """
        return cls(
            plan=OrganizePlan.from_dict(data["plan"]),
            completed_at=data.get("completed_at", time.time()),
            success=data.get("success", True),
            error=data.get("error"),
        )

    def to_json(self) -> str:
        """Serialize to pretty-printed JSON string.

        Returns:
            JSON string with indentation for readability.
        """
        return json.dumps(self.to_dict(), indent=2)

    @classmethod
    def from_json(cls, json_str: str) -> UndoLog:
        """Deserialize from JSON string.

        Args:
            json_str: JSON string from to_json().

        Returns:
            New UndoLog instance.
        """
        return cls.from_dict(json.loads(json_str))
