from __future__ import annotations

import textwrap
from pathlib import Path

import pytest

from dockervault.classifier import classify_compose


def write_compose(tmp_path: Path, content: str) -> Path:
    compose_file = tmp_path / "docker-compose.yml"
    compose_file.write_text(textwrap.dedent(content).strip() + "\n", encoding="utf-8")
    return compose_file


def find_entry(entries, service: str, target: str):
    for entry in entries:
        if entry.service == service and entry.target == target:
            return entry
    raise AssertionError(f"No entry found for service={service!r} target={target!r}")


def test_bind_mount_relative_path_is_resolved_and_classified_critical(tmp_path: Path):
    data_dir = tmp_path / "db"
    data_dir.mkdir()

    compose_file = write_compose(
        tmp_path,
        """
        services:
          db:
            image: mariadb:11
            volumes:
              - ./db:/var/lib/mysql
        """,
    )

    entries = classify_compose(compose_file)

    assert len(entries) == 1
    entry = find_entry(entries, "db", "/var/lib/mysql")

    assert entry.source == data_dir.resolve()
    assert entry.classification == "critical"
    assert entry.exists is True
    assert entry.service == "db"
    assert "mariadb" in entry.reason or "critical" in entry.reason


def test_named_volume_resolves_and_is_classified_critical(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
    mountpoint = tmp_path / "docker-volumes" / "project_dbdata" / "_data"
    mountpoint.mkdir(parents=True)

    compose_file = write_compose(
        tmp_path,
        """
        services:
          db:
            image: mariadb:11
            volumes:
              - dbdata:/var/lib/mysql

        volumes:
          dbdata:
        """,
    )

    monkeypatch.setattr("dockervault.classifier.docker_available", lambda: True)
    monkeypatch.setattr(
        "dockervault.classifier.run_docker_volume_inspect",
        lambda volume_name: {"Mountpoint": str(mountpoint)} if volume_name == "dbdata" else None,
    )

    entries = classify_compose(compose_file)

    assert len(entries) == 1
    entry = find_entry(entries, "db", "/var/lib/mysql")

    assert entry.source == mountpoint
    assert entry.classification == "critical"
    assert entry.exists is True
    assert "named volume 'dbdata'" in entry.reason


def test_named_volume_unresolved_falls_back_to_review(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
    compose_file = write_compose(
        tmp_path,
        """
        services:
          app:
            image: redis:7
            volumes:
              - cachedata:/data

        volumes:
          cachedata:
        """,
    )

    monkeypatch.setattr("dockervault.classifier.docker_available", lambda: True)
    monkeypatch.setattr("dockervault.classifier.run_docker_volume_inspect", lambda volume_name: None)

    entries = classify_compose(compose_file)

    assert len(entries) == 1
    entry = find_entry(entries, "app", "/data")

    assert entry.classification == "review"
    assert entry.exists is False
    assert "__named_volume_unresolved__" in str(entry.source)
    assert "could not be resolved" in entry.reason


def test_named_volume_review_when_docker_not_available(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
    compose_file = write_compose(
        tmp_path,
        """
        services:
          app:
            image: redis:7
            volumes:
              - cachedata:/data

        volumes:
          cachedata:
        """,
    )

    monkeypatch.setattr("dockervault.classifier.docker_available", lambda: False)

    entries = classify_compose(compose_file)

    assert len(entries) == 1
    entry = find_entry(entries, "app", "/data")

    assert entry.classification == "review"
    assert entry.exists is False
    assert "docker CLI not available" in entry.reason


def test_image_rule_overrides_generic_logic_for_nginx_logs(tmp_path: Path):
    logs_dir = tmp_path / "logs"
    logs_dir.mkdir()

    compose_file = write_compose(
        tmp_path,
        """
        services:
          nginx:
            image: nginx:latest
            volumes:
              - ./logs:/var/log/nginx
        """,
    )

    entries = classify_compose(compose_file)

    assert len(entries) == 1
    entry = find_entry(entries, "nginx", "/var/log/nginx")

    assert entry.source == logs_dir.resolve()
    assert entry.classification == "optional"
    assert entry.exists is True


def test_dedupe_prefers_stronger_classification_for_same_source(tmp_path: Path):
    shared_dir = tmp_path / "shared"
    shared_dir.mkdir()

    compose_file = write_compose(
        tmp_path,
        f"""
        services:
          db:
            image: mariadb:11
            volumes:
              - {shared_dir}:/var/lib/mysql

          backup:
            image: busybox
            volumes:
              - {shared_dir}:/backup
        """,
    )

    entries = classify_compose(compose_file)

    assert len(entries) == 1
    entry = entries[0]

    assert entry.source == shared_dir.resolve()
    assert entry.classification == "critical"
    assert entry.exists is True
    assert "mariadb" in entry.reason or "/var/lib/mysql" in entry.reason


def test_top_level_volume_name_override_is_used(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
    mountpoint = tmp_path / "docker-volumes" / "real-db-volume" / "_data"
    mountpoint.mkdir(parents=True)

    compose_file = write_compose(
        tmp_path,
        """
        services:
          db:
            image: postgres:16
            volumes:
              - dbdata:/var/lib/postgresql/data

        volumes:
          dbdata:
            name: real-db-volume
        """,
    )

    seen = []

    def fake_inspect(volume_name: str):
        seen.append(volume_name)
        if volume_name == "real-db-volume":
            return {"Mountpoint": str(mountpoint)}
        return None

    monkeypatch.setattr("dockervault.classifier.docker_available", lambda: True)
    monkeypatch.setattr("dockervault.classifier.run_docker_volume_inspect", fake_inspect)

    entries = classify_compose(compose_file)

    entry = find_entry(entries, "db", "/var/lib/postgresql/data")
    assert entry.source == mountpoint
    assert entry.classification == "critical"
    assert "real-db-volume" in entry.reason
    assert seen[0] == "real-db-volume"


def test_external_volume_tries_raw_name(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
    mountpoint = tmp_path / "docker-volumes" / "shared-prod-data" / "_data"
    mountpoint.mkdir(parents=True)

    compose_file = write_compose(
        tmp_path,
        """
        services:
          app:
            image: redis:7
            volumes:
              - shareddata:/data

        volumes:
          shareddata:
            external: true
        """,
    )

    tried = []

    def fake_inspect(volume_name: str):
        tried.append(volume_name)
        if volume_name == "shareddata":
            return {"Mountpoint": str(mountpoint)}
        return None

    monkeypatch.setattr("dockervault.classifier.docker_available", lambda: True)
    monkeypatch.setattr("dockervault.classifier.run_docker_volume_inspect", fake_inspect)

    entries = classify_compose(compose_file)

    entry = find_entry(entries, "app", "/data")
    assert entry.source == mountpoint
    assert entry.classification == "critical"
    assert "shareddata" in tried


def test_anonymous_volume_becomes_review(tmp_path: Path):
    compose_file = write_compose(
        tmp_path,
        """
        services:
          app:
            image: busybox
            volumes:
              - /data
        """,
    )

    entries = classify_compose(compose_file)

    assert len(entries) == 1
    entry = find_entry(entries, "app", "/data")

    assert entry.classification == "review"
    assert entry.exists is False
    assert "__anonymous_volume__" in str(entry.source) or "__anonymous__" in str(entry.source)
