from __future__ import annotations

import argparse
import sys
from pathlib import Path
from typing import Any, Iterable

from .borg import (
    build_borg_create_command,
    command_to_shell,
    run_borg_create,
)
from .classifier import classify_compose


def _get_value(obj: Any, *names: str, default: Any = None) -> Any:
    for name in names:
        if isinstance(obj, dict) and name in obj:
            return obj[name]
        if hasattr(obj, name):
            return getattr(obj, name)
    return default


def _normalize_entries(entries: Any) -> list[dict[str, Any]]:
    if not entries:
        return []

    normalized: list[dict[str, Any]] = []

    for entry in entries:
        if isinstance(entry, dict):
            normalized.append(
                {
                    "source": (
                        entry.get("source")
                        or entry.get("path")
                        or entry.get("host_path")
                        or entry.get("src")
                    ),
                    "service": entry.get("service"),
                    "target": (
                        entry.get("target")
                        or entry.get("mount_target")
                        or entry.get("container_path")
                        or entry.get("destination")
                    ),
                    "classification": (
                        entry.get("classification")
                        or entry.get("priority")
                        or entry.get("category")
                        or entry.get("kind")
                    ),
                    "reason": entry.get("reason"),
                }
            )
            continue

        normalized.append(
            {
                "source": _get_value(entry, "source", "path", "host_path", "src"),
                "service": _get_value(entry, "service"),
                "target": _get_value(
                    entry, "target", "mount_target", "container_path", "destination"
                ),
                "classification": _get_value(
                    entry, "classification", "priority", "category", "kind"
                ),
                "reason": _get_value(entry, "reason"),
            }
        )

    return normalized


def _extract_plan_sections(
    plan: Any,
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]:
    include_entries = _normalize_entries(
        _get_value(plan, "include", "include_paths", "includes", default=[])
    )
    review_entries = _normalize_entries(
        _get_value(plan, "review", "review_paths", "reviews", default=[])
    )
    skip_entries = _normalize_entries(
        _get_value(plan, "skip", "skip_paths", "skips", default=[])
    )

    return include_entries, review_entries, skip_entries


def _entry_path(entry: dict[str, Any]) -> str:
    return str(entry.get("source") or "(unknown)")


def _entry_label(entry: dict[str, Any]) -> str:
    classification = entry.get("classification") or "unknown"
    service = entry.get("service") or "unknown"
    target = entry.get("target") or "unknown"
    reason = entry.get("reason")

    label = f"[{classification}] service={service} target={target}"
    if reason:
        label += f" reason={reason}"
    return label


def _print_section(title: str, entries: Iterable[dict[str, Any]]) -> None:
    entries = list(entries)
    print(f"{title}:")
    if not entries:
        print("  - (none)")
        return

    for entry in entries:
        print(f"  - {_entry_path(entry):<40} {_entry_label(entry)}")


def _collect_include_paths(include_entries: Iterable[dict[str, Any]]) -> list[str]:
    paths: list[str] = []
    seen: set[str] = set()

    for entry in include_entries:
        path = _entry_path(entry)
        if path == "(unknown)" or path in seen:
            continue
        seen.add(path)
        paths.append(path)

    return paths


def _print_borg_plan(
    compose_path: Path,
    project_root: Path,
    include_entries: list[dict[str, Any]],
    review_entries: list[dict[str, Any]],
    skip_entries: list[dict[str, Any]],
    repo: str | None,
) -> None:
    print()
    print("Borg Backup Plan")
    print("================")
    print(f"Compose file: {compose_path}")
    print(f"Project root: {project_root}")
    print()

    _print_section("INCLUDE PATHS", include_entries)
    print()
    _print_section("REVIEW PATHS", review_entries)
    print()
    _print_section("SKIP PATHS", skip_entries)

    include_paths = _collect_include_paths(include_entries)

    if repo and include_paths:
        command = build_borg_create_command(
            repo=repo,
            include_paths=include_paths,
        )
        print()
        print("Suggested borg create command")
        print("=============================")
        print(command_to_shell(command))


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="dockervault",
        description="DockerVault - intelligent Docker backup discovery",
    )

    parser.add_argument("compose", help="Path to docker-compose.yml")

    parser.add_argument(
        "--borg",
        action="store_true",
        help="Show borg backup plan and suggested command",
    )

    parser.add_argument(
        "--run-borg",
        action="store_true",
        help="Run borg create using discovered include paths",
    )

    parser.add_argument(
        "--repo",
        help="Borg repository path, e.g. /mnt/backups/borg/dockervault",
    )

    parser.add_argument(
        "--passphrase",
        help="Optional borg passphrase",
    )

    parser.add_argument(
        "--quiet",
        action="store_true",
        help="Suppress borg stdout/stderr output during execution",
    )

    parser.add_argument(
        "--automation",
        action="store_true",
        help="Automation mode: minimal output, non-interactive behavior",
    )

    parser.add_argument(
        "--fail-on-review",
        action="store_true",
        help="Exit with code 4 if review paths are present",
    )

    return parser


def main() -> None:
    parser = build_parser()
    args = parser.parse_args()

    compose_path = Path(args.compose).expanduser().resolve()

    if not compose_path.exists():
        print(f"Error: compose file not found: {compose_path}", file=sys.stderr)
        sys.exit(1)

    try:
        plan = classify_compose(compose_path)
    except Exception as exc:
        print(f"Error: failed to classify compose file: {exc}", file=sys.stderr)
        sys.exit(1)

    include_entries, review_entries, skip_entries = _extract_plan_sections(plan)
    include_paths = _collect_include_paths(include_entries)
    project_root = compose_path.parent

    should_show_plan = args.borg or (not args.automation and not args.quiet)

    if should_show_plan:
        _print_borg_plan(
            compose_path=compose_path,
            project_root=project_root,
            include_entries=include_entries,
            review_entries=review_entries,
            skip_entries=skip_entries,
            repo=args.repo,
        )

    if args.fail_on_review and review_entries:
        if args.automation or args.quiet:
            print("REVIEW required", file=sys.stderr)
        else:
            print()
            print("Review required before automated backup can proceed.", file=sys.stderr)
        sys.exit(4)

    if args.run_borg:
        if not args.repo:
            print("Error: --run-borg requires --repo", file=sys.stderr)
            sys.exit(2)

        if not include_paths:
            print("Error: no include paths found for borg backup", file=sys.stderr)
            sys.exit(3)

        if not args.quiet:
            print()
            print("Running borg backup...")
            print("======================")

        exit_code = run_borg_create(
            repo=args.repo,
            include_paths=include_paths,
            passphrase=args.passphrase,
            quiet=args.quiet,
            stats=not args.quiet,
            progress=not args.quiet,
        )

        if exit_code != 0:
            print(f"Error: borg exited with status {exit_code}", file=sys.stderr)
            sys.exit(exit_code)

        if not args.quiet:
            print()
            print("Borg backup completed successfully.")

    sys.exit(0)


if __name__ == "__main__":
    main()
