from __future__ import annotations

import shutil
from dataclasses import dataclass
from pathlib import Path

from src.errors import UserError
from src.organize.path_builder import FolderPath


@dataclass(frozen=True)
class OrganizeResult:
    dest_dir: Path
    dry_run: bool
    files_planned: int
    files_copied: int
    files_moved: int

    def to_human_summary(self) -> str:
        mode = "DRY-RUN" if self.dry_run else "RUN"
        return (
            f"{mode} organize summary\n"
            f"- dest_dir: {self.dest_dir}\n"
            f"- planned: {self.files_planned}\n"
            f"- copied: {self.files_copied}\n"
            f"- moved: {self.files_moved}"
        )


def organize_files(
    *,
    root_dir: Path,
    folder_path: FolderPath,
    source_files: list[Path],
    strategy: str,
    dry_run: bool,
) -> OrganizeResult:
    dest_dir = _dest_dir(root_dir, folder_path)
    planned = len(source_files)

    if not dry_run:
        _ensure_dir(dest_dir)

    copied = 0
    moved = 0
    for src in source_files:
        _validate_source_file(src)
        dest = dest_dir / src.name
        if dest.exists():
            raise UserError(f"Refusing to overwrite existing file: {dest}")
        if dry_run:
            continue
        if strategy == "copy":
            shutil.copy2(src, dest)
            copied += 1
        elif strategy == "move":
            shutil.move(str(src), str(dest))
            moved += 1
        else:
            raise UserError(f"Unknown strategy: {strategy}")

    return OrganizeResult(
        dest_dir=dest_dir,
        dry_run=dry_run,
        files_planned=planned,
        files_copied=copied,
        files_moved=moved,
    )


def _dest_dir(root_dir: Path, folder_path: FolderPath) -> Path:
    root = _validate_root_dir(root_dir)
    rel = Path(*folder_path.relpath().strip("/").split("/"))
    dest = root / rel
    if not _is_within_root(dest, root):
        raise UserError("Refusing to write outside of root_dir.")
    return dest


def _validate_root_dir(root_dir: Path) -> Path:
    try:
        root = root_dir.resolve(strict=False)
    except Exception as exc:
        raise UserError(f"Invalid root_dir: {root_dir} ({exc})") from exc
    if root.exists() and not root.is_dir():
        raise UserError(f"Root path exists but is not a directory: {root}")
    return root


def _is_within_root(path: Path, root: Path) -> bool:
    try:
        path_r = path.resolve(strict=False)
        root_r = root.resolve(strict=False)
    except Exception:
        return False
    return root_r == path_r or root_r in path_r.parents


def _ensure_dir(path: Path) -> None:
    try:
        path.mkdir(parents=True, exist_ok=True)
    except OSError as exc:
        raise UserError(f"Failed to create directory: {path} ({exc})") from exc


def _validate_source_file(path: Path) -> None:
    if not path.exists():
        raise UserError(f"Source file does not exist: {path}")
    if not path.is_file():
        raise UserError(f"Source path is not a file: {path}")
