from __future__ import annotations

from pathlib import Path
from typing import Any

import yaml


CRITICAL_TARGET_PATTERNS = (
    "/var/lib/mysql",
    "/var/lib/postgresql",
    "/var/lib/postgres",
    "/var/lib/mariadb",
    "/data",
    "/config",
    "/var/www",
    "/srv",
    "/app/data",
    "/bitnami",
    "/var/opt",
    "/var/lib/redis",
    "/redis",
    "/var/lib/mongodb",
    "/mongodb",
)

OPTIONAL_TARGET_PATTERNS = (
    "/var/log",
    "/logs",
    "/log",
    "/tmp",
    "/cache",
    "/var/cache",
    "/run",
)

OPTIONAL_SOURCE_PATTERNS = (
    "logs",
    "log",
    "cache",
    "tmp",
    "temp",
)

SKIP_TARGET_PATTERNS = (
    "/dev",
    "/proc",
    "/sys",
    "/run",
    "/tmp",
)

SKIP_SOURCE_PATTERNS = (
    "/var/run/docker.sock",
    "docker.sock",
)


def load_compose(compose_file: Path) -> dict[str, Any]:
    with compose_file.open("r", encoding="utf-8") as f:
        data = yaml.safe_load(f) or {}

    if not isinstance(data, dict):
        raise ValueError("Compose file did not parse into a dictionary")

    return data


def is_bind_mount(volume: Any) -> bool:
    if isinstance(volume, str):
        return ":" in volume

    if isinstance(volume, dict):
        return volume.get("type") == "bind"

    return False


def parse_volume_entry(
    volume: Any,
    compose_file: Path,
) -> dict[str, str] | None:
    project_root = compose_file.parent.resolve()

    if isinstance(volume, str):
        parts = volume.split(":")
        if len(parts) < 2:
            return None

        source_raw = parts[0].strip()
        target = parts[1].strip()

        if not source_raw or not target:
            return None

        # Named volumes should not be treated as bind mounts
        if not source_raw.startswith(("/", ".", "~")):
            return None

        source = resolve_source_path(source_raw, project_root)

        return {
            "source": str(source),
            "target": target,
        }

    if isinstance(volume, dict):
        if volume.get("type") != "bind":
            return None

        source_raw = str(volume.get("source", "")).strip()
        target = str(volume.get("target", "")).strip()

        if not source_raw or not target:
            return None

        source = resolve_source_path(source_raw, project_root)

        return {
            "source": str(source),
            "target": target,
        }

    return None


def resolve_source_path(source_raw: str, project_root: Path) -> Path:
    source_path = Path(source_raw).expanduser()

    if not source_path.is_absolute():
        source_path = (project_root / source_path).resolve()
    else:
        source_path = source_path.resolve()

    return source_path


def classify_mount(
    service_name: str,
    source: str,
    target: str,
) -> tuple[str, str, str]:
    source_lower = source.lower()
    target_lower = target.lower()

    for pattern in SKIP_SOURCE_PATTERNS:
        if pattern in source_lower:
            return "skip", "optional", "docker runtime socket"

    for pattern in SKIP_TARGET_PATTERNS:
        if target_lower == pattern or target_lower.startswith(pattern + "/"):
            return "skip", "optional", "runtime/system path"

    for pattern in CRITICAL_TARGET_PATTERNS:
        if target_lower == pattern or target_lower.startswith(pattern + "/"):
            return "include", "critical", "persistent app data"

    for pattern in OPTIONAL_TARGET_PATTERNS:
        if target_lower == pattern or target_lower.startswith(pattern + "/"):
            return "skip", "optional", "logs/cache/temp path"

    source_name = Path(source).name.lower()
    for pattern in OPTIONAL_SOURCE_PATTERNS:
        if pattern in source_name:
            return "skip", "optional", "logs/cache/temp source"

    return "review", "medium", "unknown bind mount"


def classify_service_mounts(
    service_name: str,
    service_data: dict[str, Any],
    compose_file: Path,
) -> list[dict[str, str]]:
    results: list[dict[str, str]] = []

    volumes = service_data.get("volumes", [])
    if not isinstance(volumes, list):
        return results

    for volume in volumes:
        if not is_bind_mount(volume):
            continue

        parsed = parse_volume_entry(volume, compose_file)
        if not parsed:
            continue

        bucket, priority, reason = classify_mount(
            service_name=service_name,
            source=parsed["source"],
            target=parsed["target"],
        )

        results.append(
            {
                "bucket": bucket,
                "priority": priority,
                "reason": reason,
                "service": service_name,
                "source": parsed["source"],
                "target": parsed["target"],
            }
        )

    return results


def deduplicate_items(items: list[dict[str, str]]) -> list[dict[str, str]]:
    seen: set[tuple[str, str, str, str]] = set()
    deduped: list[dict[str, str]] = []

    for item in items:
        key = (
            item["service"],
            item["source"],
            item["target"],
            item["bucket"],
        )
        if key in seen:
            continue
        seen.add(key)
        deduped.append(item)

    return deduped


def sort_items(items: list[dict[str, str]]) -> list[dict[str, str]]:
    priority_order = {
        "critical": 0,
        "high": 1,
        "medium": 2,
        "low": 3,
        "optional": 4,
    }

    return sorted(
        items,
        key=lambda item: (
            priority_order.get(item["priority"], 99),
            item["service"],
            item["source"],
            item["target"],
        ),
    )


def classify_compose(compose_file: str | Path) -> dict[str, Any]:
    compose_path = Path(compose_file).resolve()
    data = load_compose(compose_path)

    services = data.get("services", {})
    if not isinstance(services, dict):
        raise ValueError("Compose file does not contain a valid 'services' section")

    all_items: list[dict[str, str]] = []

    for service_name, service_data in services.items():
        if not isinstance(service_data, dict):
            continue

        all_items.extend(
            classify_service_mounts(
                service_name=service_name,
                service_data=service_data,
                compose_file=compose_path,
            )
        )

    all_items = deduplicate_items(all_items)
    all_items = sort_items(all_items)

    include = [strip_bucket(item) for item in all_items if item["bucket"] == "include"]
    review = [strip_bucket(item) for item in all_items if item["bucket"] == "review"]
    skip = [strip_bucket(item) for item in all_items if item["bucket"] == "skip"]

    return {
        "include": include,
        "review": review,
        "skip": skip,
    }


def strip_bucket(item: dict[str, str]) -> dict[str, str]:
    return {
        "service": item["service"],
        "source": item["source"],
        "target": item["target"],
        "priority": item["priority"],
        "reason": item["reason"],
    }
