"""
Tests for kodara.clone.generator — clone-pattern feature.

All tests are offline: RemoteScanner and build_fingerprint are mocked
so no real network calls happen.

Run: pytest tests/test_clone_pattern.py -v
"""

from __future__ import annotations

import time
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

from kodara.clone.generator import CloneResult, clone_pattern


# ── Helpers ───────────────────────────────────────────────────────────────────


def _make_scan_summary(url: str = "github.com/test/repo") -> MagicMock:
    """Return a minimal ScanSummary mock."""
    summary = MagicMock()
    summary.url = url
    summary.file_count = 10
    summary.language_counts = {"python": 10}
    return summary


def _make_dna(patterns: dict[str, list[str]] | None = None) -> MagicMock:
    """Return a minimal ProjectDNA mock."""
    dna = MagicMock()
    dna.detected_patterns = patterns or {}
    dna.hub_modules = []
    dna.language_profile = {"python": 10}
    dna.complexity_score = 1.0
    dna.layer_structure = []
    return dna


def _patch_scan(scan_summary=None, dna=None):
    """Return a context manager pair that mocks scan_remote and build_fingerprint."""
    if scan_summary is None:
        scan_summary = _make_scan_summary()
    if dna is None:
        dna = _make_dna()

    p_scan = patch(
        "kodara.clone.generator.scan_remote",
        return_value=scan_summary,
    )
    p_dna = patch(
        "kodara.clone.generator.build_fingerprint",
        return_value=dna,
    )
    return p_scan, p_dna


# ── Basic success ─────────────────────────────────────────────────────────────


def test_clone_pattern_returns_clone_result(tmp_path: Path):
    """clone_pattern returns a CloneResult instance on success."""
    target = tmp_path / "new-project"
    p_scan, p_dna = _patch_scan()
    with p_scan, p_dna:
        result = clone_pattern("github.com/test/repo", str(target))
    assert isinstance(result, CloneResult)
    assert result.error is None


def test_creates_target_directory_if_not_exists(tmp_path: Path):
    """clone_pattern creates the target directory when it doesn't exist."""
    target = tmp_path / "brand-new"
    assert not target.exists()

    p_scan, p_dna = _patch_scan()
    with p_scan, p_dna:
        result = clone_pattern("github.com/test/repo", str(target))

    assert result.error is None
    assert target.exists()


def test_error_if_target_non_empty(tmp_path: Path):
    """Returns error (not exception) when target directory is non-empty."""
    target = tmp_path / "existing"
    target.mkdir()
    (target / "some_file.py").write_text("existing content")

    p_scan, p_dna = _patch_scan()
    with p_scan, p_dna:
        result = clone_pattern("github.com/test/repo", str(target))

    assert result.error == "Target directory is not empty"


def test_creates_main_py_always(tmp_path: Path):
    """main.py is always created regardless of detected patterns."""
    target = tmp_path / "proj"
    p_scan, p_dna = _patch_scan(dna=_make_dna({}))
    with p_scan, p_dna:
        result = clone_pattern("github.com/test/repo", str(target))

    assert result.error is None
    assert (target / "main.py").exists()


def test_creates_config_py_always(tmp_path: Path):
    """config.py is always created as part of base structure."""
    target = tmp_path / "proj"
    p_scan, p_dna = _patch_scan(dna=_make_dna({}))
    with p_scan, p_dna:
        result = clone_pattern("github.com/test/repo", str(target))

    assert (target / "config.py").exists()


def test_creates_base_files_always(tmp_path: Path):
    """requirements.txt and README.md are always created."""
    target = tmp_path / "proj"
    p_scan, p_dna = _patch_scan(dna=_make_dna({}))
    with p_scan, p_dna:
        clone_pattern("github.com/test/repo", str(target))

    assert (target / "requirements.txt").exists()
    assert (target / "README.md").exists()


# ── Pattern-driven directories ────────────────────────────────────────────────


def test_creates_auth_dir_for_jwt_pattern(tmp_path: Path):
    """auth/ directory is created when JWT auth pattern is detected."""
    target = tmp_path / "proj"
    dna = _make_dna({"Auth": ["JWT"], "Routing": [], "Database": [],
                     "Caching": [], "Queue": [], "Testing": []})
    p_scan, p_dna = _patch_scan(dna=dna)
    with p_scan, p_dna:
        result = clone_pattern("github.com/test/repo", str(target))

    assert result.error is None
    assert (target / "auth").is_dir()
    assert (target / "auth" / "middleware.py").exists()
    assert (target / "auth" / "models.py").exists()


def test_creates_db_dir_for_repository_pattern(tmp_path: Path):
    """db/ directory is created when Repository database pattern is detected."""
    target = tmp_path / "proj"
    dna = _make_dna({"Auth": [], "Routing": [], "Database": ["Repository"],
                     "Caching": [], "Queue": [], "Testing": []})
    p_scan, p_dna = _patch_scan(dna=dna)
    with p_scan, p_dna:
        result = clone_pattern("github.com/test/repo", str(target))

    assert result.error is None
    assert (target / "db").is_dir()
    assert (target / "db" / "models.py").exists()
    assert (target / "db" / "repository.py").exists()


def test_creates_routes_dir_for_routing_pattern(tmp_path: Path):
    """routes/ directory is created when routing pattern is detected."""
    target = tmp_path / "proj"
    dna = _make_dna({"Auth": [], "Routing": ["Decorator"], "Database": [],
                     "Caching": [], "Queue": [], "Testing": []})
    p_scan, p_dna = _patch_scan(dna=dna)
    with p_scan, p_dna:
        result = clone_pattern("github.com/test/repo", str(target))

    assert result.error is None
    assert (target / "routes").is_dir()
    assert (target / "routes" / "__init__.py").exists()


def test_creates_tests_dir_for_testing_pattern(tmp_path: Path):
    """tests/ directory is created when testing pattern is detected."""
    target = tmp_path / "proj"
    dna = _make_dna({"Auth": [], "Routing": [], "Database": [],
                     "Caching": [], "Queue": [], "Testing": ["Unit Tests"]})
    p_scan, p_dna = _patch_scan(dna=dna)
    with p_scan, p_dna:
        result = clone_pattern("github.com/test/repo", str(target))

    assert result.error is None
    assert (target / "tests").is_dir()
    assert (target / "tests" / "conftest.py").exists()


def test_creates_cache_dir_for_caching_pattern(tmp_path: Path):
    """cache/ directory is created when caching pattern is detected."""
    target = tmp_path / "proj"
    dna = _make_dna({"Auth": [], "Routing": [], "Database": [],
                     "Caching": ["Redis"], "Queue": [], "Testing": []})
    p_scan, p_dna = _patch_scan(dna=dna)
    with p_scan, p_dna:
        result = clone_pattern("github.com/test/repo", str(target))

    assert result.error is None
    assert (target / "cache").is_dir()
    assert (target / "cache" / "client.py").exists()


def test_creates_queue_dir_for_queue_pattern(tmp_path: Path):
    """queue/ directory is created when queue pattern is detected."""
    target = tmp_path / "proj"
    dna = _make_dna({"Auth": [], "Routing": [], "Database": [],
                     "Caching": [], "Queue": ["Celery"], "Testing": []})
    p_scan, p_dna = _patch_scan(dna=dna)
    with p_scan, p_dna:
        result = clone_pattern("github.com/test/repo", str(target))

    assert result.error is None
    assert (target / "queue").is_dir()
    assert (target / "queue" / "tasks.py").exists()


# ── File content ──────────────────────────────────────────────────────────────


def test_files_contain_generated_comment(tmp_path: Path):
    """Generated files contain the 'Generated by kodara clone-pattern' comment."""
    url = "github.com/tiangolo/fastapi"
    target = tmp_path / "proj"
    p_scan, p_dna = _patch_scan(
        scan_summary=_make_scan_summary(url),
        dna=_make_dna({}),
    )
    with p_scan, p_dna:
        clone_pattern(url, str(target))

    content = (target / "main.py").read_text(encoding="utf-8")
    assert "Generated by kodara clone-pattern" in content
    assert url in content


# ── Result metadata ───────────────────────────────────────────────────────────


def test_patterns_applied_populated(tmp_path: Path):
    """patterns_applied list is populated from detected DNA patterns."""
    target = tmp_path / "proj"
    dna = _make_dna({"Auth": ["JWT", "OAuth"], "Routing": ["Decorator"],
                     "Database": [], "Caching": [], "Queue": [], "Testing": []})
    p_scan, p_dna = _patch_scan(dna=dna)
    with p_scan, p_dna:
        result = clone_pattern("github.com/test/repo", str(target))

    assert "JWT" in result.patterns_applied
    assert "OAuth" in result.patterns_applied
    assert "Decorator" in result.patterns_applied


def test_dirs_created_and_files_created_populated(tmp_path: Path):
    """dirs_created and files_created lists are populated after a successful run."""
    target = tmp_path / "proj"
    dna = _make_dna({"Auth": ["JWT"], "Routing": [], "Database": ["Repository"],
                     "Caching": [], "Queue": [], "Testing": []})
    p_scan, p_dna = _patch_scan(dna=dna)
    with p_scan, p_dna:
        result = clone_pattern("github.com/test/repo", str(target))

    assert result.error is None
    assert len(result.dirs_created) >= 2   # auth + db
    assert len(result.files_created) >= 4  # base files + pattern files


# ── Error handling ────────────────────────────────────────────────────────────


def test_error_returned_not_raised_on_network_failure(tmp_path: Path):
    """Network/scan errors are caught and returned in result.error, not raised."""
    target = tmp_path / "proj"
    with patch(
        "kodara.clone.generator.scan_remote",
        side_effect=ValueError("git clone failed (exit 128): repository not found"),
    ):
        result = clone_pattern("github.com/nonexistent/repo", str(target))

    assert result.error is not None
    assert "git clone failed" in result.error


def test_works_with_local_path_as_url(tmp_path: Path):
    """clone_pattern accepts a local filesystem path as the url argument."""
    target = tmp_path / "output"
    p_scan, p_dna = _patch_scan(dna=_make_dna({}))
    with p_scan, p_dna:
        result = clone_pattern(str(tmp_path / "some-local-repo"), str(target))

    assert result.error is None
    assert (target / "main.py").exists()


def test_empty_patterns_creates_base_structure(tmp_path: Path):
    """When no patterns are detected, the base structure (main.py, config.py) is still created."""
    target = tmp_path / "proj"
    p_scan, p_dna = _patch_scan(dna=_make_dna({}))
    with p_scan, p_dna:
        result = clone_pattern("github.com/test/minimal", str(target))

    assert result.error is None
    assert (target / "main.py").exists()
    assert (target / "config.py").exists()
    # No pattern-specific dirs should have been created
    assert not (target / "auth").exists()
    assert not (target / "db").exists()
