from __future__ import annotations

import os
import uuid
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Iterable


@dataclass
class GameWritePermissionError(PermissionError):
    message: str
    preflight: dict[str, Any]

    def __str__(self) -> str:
        return self.message


def path_is_within(child: Path, parent: Path) -> bool:
    try:
        return os.path.commonpath([str(child.resolve()), str(parent.resolve())]) == str(parent.resolve())
    except Exception:
        try:
            return os.path.commonpath([str(child.absolute()), str(parent.absolute())]) == str(parent.absolute())
        except Exception:
            return False


def normalize_game_root(path: str | Path) -> Path:
    candidate = Path(path).expanduser()
    try:
        candidate = candidate.resolve()
    except Exception:
        candidate = candidate.absolute()
    if candidate.name.lower() == "content" and candidate.parent.exists():
        return candidate.parent
    return candidate


def write_probe(directory: Path) -> dict[str, Any]:
    result: dict[str, Any] = {
        "path": str(directory),
        "exists": directory.exists(),
        "isDir": directory.is_dir(),
        "writable": False,
        "message": "",
    }
    if not result["exists"]:
        result["message"] = "Folder does not exist"
        return result
    if not result["isDir"]:
        result["message"] = "Path is not a folder"
        return result

    probe = directory / f".liverylab_write_guard_{uuid.uuid4().hex}.tmp"
    try:
        with probe.open("x", encoding="utf-8") as handle:
            handle.write("LiveryLab write guard\n")
        probe.unlink()
        result["writable"] = True
        result["message"] = "Writable"
    except PermissionError as exc:
        result["message"] = "Access denied"
        result["detail"] = str(exc)
    except OSError as exc:
        result["message"] = getattr(exc, "strerror", "") or str(exc)
        result["detail"] = str(exc)
    finally:
        try:
            if probe.exists():
                probe.unlink()
        except Exception:
            pass
    return result


def _as_directory(path: Path) -> Path:
    if path.exists() and path.is_dir():
        return path
    if path.suffix:
        candidate = path.parent
    else:
        candidate = path.parent if not path.exists() else path
    while not candidate.exists() and candidate != candidate.parent:
        candidate = candidate.parent
    return candidate


def build_game_write_preflight(
    root: str | Path,
    operation: str = "game file write",
    required_paths: Iterable[str | Path] | None = None,
    require_content_cars: bool = True,
) -> dict[str, Any]:
    root_path = normalize_game_root(root)
    content_cars = root_path / "content" / "cars"
    system = root_path / "system"

    directories = [root_path]
    if require_content_cars:
        directories.append(content_cars)
    if system.exists():
        directories.append(system)
    target_rows: list[dict[str, Any]] = []
    for value in required_paths or []:
        if not str(value or "").strip():
            continue
        path = Path(value).expanduser()
        if not path.is_absolute():
            path = root_path / path
        try:
            path = path.resolve()
        except Exception:
            path = path.absolute()
        inside_root = path_is_within(path, root_path)
        target_rows.append(
            {
                "path": str(path),
                "insideRoot": inside_root,
                "exists": path.exists(),
                "isDir": path.is_dir(),
            }
        )
        directories.append(_as_directory(path))

    unique_dirs: list[Path] = []
    seen: set[str] = set()
    for directory in directories:
        key = str(directory).lower()
        if key in seen:
            continue
        seen.add(key)
        unique_dirs.append(directory)

    checks = [{"key": f"dir{index}", **write_probe(directory)} for index, directory in enumerate(unique_dirs)]
    outside_targets = [row for row in target_rows if not row["insideRoot"]]
    failed_checks = [row for row in checks if not row.get("writable")]
    warnings: list[str] = []
    program_dirs = [
        Path(value)
        for value in {
            os.environ.get("ProgramFiles", ""),
            os.environ.get("ProgramFiles(x86)", ""),
            os.environ.get("ProgramW6432", ""),
        }
        if value
    ]
    under_program_files = any(path_is_within(root_path, folder) for folder in program_dirs)
    if under_program_files:
        warnings.append("Root is inside Program Files; Windows may block writes without elevated permissions.")
    if outside_targets:
        warnings.append("One or more planned write targets are outside the selected ACE root.")

    content_ready = content_cars.exists()
    write_ready = (
        not failed_checks
        and not outside_targets
        and root_path.exists()
        and (content_ready or not require_content_cars)
    )
    if not root_path.exists():
        summary = "Root not found"
    elif require_content_cars and not content_ready:
        summary = "content/cars not found"
    elif outside_targets:
        summary = "Planned write target outside ACE root"
    elif failed_checks:
        summary = f"Root not writable: {failed_checks[0].get('message') or 'write check failed'}"
    elif not content_ready:
        summary = "Game root writable; loose content not extracted"
    else:
        summary = "Game write ready"

    return {
        "operation": operation,
        "root": str(root_path),
        "writeReady": write_ready,
        "requireContentCars": require_content_cars,
        "contentCarsExists": content_ready,
        "summary": summary,
        "underProgramFiles": under_program_files,
        "warnings": warnings,
        "targets": target_rows,
        "checks": checks,
    }


def require_game_write_ready(
    root: str | Path,
    operation: str = "game file write",
    required_paths: Iterable[str | Path] | None = None,
    require_content_cars: bool = True,
) -> dict[str, Any]:
    preflight = build_game_write_preflight(root, operation, required_paths, require_content_cars=require_content_cars)
    if preflight.get("writeReady"):
        return preflight
    message = (
        f"Cannot write game files for {operation}: {preflight.get('summary')}. "
        f"Selected root: {preflight.get('root')}"
    )
    warnings = preflight.get("warnings") or []
    if warnings:
        message += f" ({'; '.join(str(row) for row in warnings)})"
    raise GameWritePermissionError(message, preflight)
