from conftest import make_event, make_issue, make_mr, make_pipeline, make_vuln

from project_hub.core.cache import Cache
from project_hub.core.models import EventType


# --- Tests ---


async def test_migrations_applied(cache: Cache):
    async with cache._db.execute("SELECT name FROM migrations_applied ORDER BY name") as cur:
        applied = [row[0] for row in await cur.fetchall()]
    assert len(applied) >= 3
    assert "001_initial.sql" in applied
    assert "002_mr_assignees_reviewers.sql" in applied
    assert "003_issues.sql" in applied


async def test_store_and_read_mrs(cache: Cache):
    mrs = [make_mr(1), make_mr(2)]
    await cache.store_mrs("repo-alpha", mrs)
    result = await cache.get_mrs("repo-alpha")
    assert len(result) == 2
    ids = {mr.iid for mr in result}
    assert ids == {1, 2}
    for mr in result:
        assert mr.repo_name == "repo-alpha"
        assert mr.author == "alice"
        assert mr.state == "opened"
        assert mr.target_branch == "main"
        assert mr.approved is False
        assert mr.sha == "abc123"
    mr1 = next(m for m in result if m.iid == 1)
    assert mr1.title == "MR 1"
    assert mr1.source_branch == "feature/1"
    assert mr1.web_url == "https://gitlab.com/mr/1"
    assert mr1.user_notes_count == 0
    mr2 = next(m for m in result if m.iid == 2)
    assert mr2.title == "MR 2"
    assert mr2.source_branch == "feature/2"
    assert mr2.web_url == "https://gitlab.com/mr/2"
    assert mr2.user_notes_count == 0


async def test_store_mrs_replaces_existing(cache: Cache):
    await cache.store_mrs("repo-alpha", [make_mr(1), make_mr(2)])
    await cache.store_mrs("repo-alpha", [make_mr(3)])
    result = await cache.get_mrs("repo-alpha")
    assert len(result) == 1
    assert result[0].iid == 3


async def test_store_and_read_pipelines(cache: Cache):
    pipelines = [make_pipeline(10), make_pipeline(11)]
    await cache.store_pipelines("repo-alpha", pipelines)
    result = await cache.get_pipelines("repo-alpha")
    assert len(result) == 2
    assert {p.id for p in result} == {10, 11}
    assert all(p.repo_name == "repo-alpha" for p in result)


async def test_store_and_read_vulns(cache: Cache):
    vulns = [make_vuln(200), make_vuln(201)]
    await cache.store_vulns("repo-alpha", vulns)
    result = await cache.get_vulns("repo-alpha")
    assert len(result) == 2
    assert {v.id for v in result} == {200, 201}
    assert all(v.severity == "high" for v in result)


async def test_store_and_read_events(cache: Cache):
    events = [make_event(EventType.PIPELINE_FAILED), make_event(EventType.MR_APPROVED)]
    await cache.store_events(events)
    result = await cache.get_unseen_events()
    assert len(result) == 2
    types = {e.type for e in result}
    assert EventType.PIPELINE_FAILED in types
    assert EventType.MR_APPROVED in types
    assert all(not e.seen for e in result)


async def test_mark_events_seen(cache: Cache):
    await cache.store_events([make_event()])
    unseen = await cache.get_unseen_events()
    assert len(unseen) == 1
    event_id = unseen[0].id
    assert event_id is not None
    await cache.mark_events_seen([event_id])
    unseen_after = await cache.get_unseen_events()
    assert len(unseen_after) == 0


async def test_mark_all_events_seen(cache: Cache):
    await cache.store_events([make_event(), make_event(EventType.MR_MERGED)])
    unseen = await cache.get_unseen_events()
    assert len(unseen) == 2
    await cache.mark_all_events_seen()
    assert len(await cache.get_unseen_events()) == 0


async def test_store_and_read_issues(cache: Cache):
    issues = [make_issue(1), make_issue(2)]
    await cache.store_issues("repo-alpha", issues)
    result = await cache.get_issues("repo-alpha")
    assert len(result) == 2
    iids = {i.iid for i in result}
    assert iids == {1, 2}
    assert all(i.repo_name == "repo-alpha" for i in result)
    assert all(i.author == "alice" for i in result)
    assert result[0].labels == ["bug"]
    assert result[0].assignees == ["alice"]
    assert result[0].participants == ["alice", "bob"]


async def test_store_issues_replaces_existing(cache: Cache):
    await cache.store_issues("repo-alpha", [make_issue(1), make_issue(2)])
    await cache.store_issues("repo-alpha", [make_issue(3)])
    result = await cache.get_issues("repo-alpha")
    assert len(result) == 1
    assert result[0].iid == 3


async def test_get_issues_all_repos(cache: Cache):
    await cache.store_issues("repo-alpha", [make_issue(1)])
    await cache.store_issues("repo-beta", [make_issue(2, repo_name="repo-beta")])
    result = await cache.get_issues()
    assert len(result) == 2


async def test_reinitialize_is_idempotent(cache: Cache):
    # Store data, reinitialize, data should survive
    await cache.store_mrs("repo-alpha", [make_mr(1)])
    await cache.initialize()
    result = await cache.get_mrs("repo-alpha")
    assert len(result) == 1


async def test_maintenance_purges_old_seen_events(cache: Cache):
    """Old seen events (>7 days) are purged by maintenance."""
    from datetime import datetime, timedelta
    # Insert an event manually with old timestamp
    old_ts = (datetime.now() - timedelta(days=10)).isoformat()
    await cache._db.execute(
        "INSERT INTO events (type, repo_name, summary, detail, created_at, seen) VALUES (?, ?, ?, ?, ?, 1)",
        ("PIPELINE_FAILED", "repo-alpha", "old event", "", old_ts),
    )
    # Insert a recent seen event that should survive
    recent_ts = datetime.now().isoformat()
    await cache._db.execute(
        "INSERT INTO events (type, repo_name, summary, detail, created_at, seen) VALUES (?, ?, ?, ?, ?, 1)",
        ("PIPELINE_FAILED", "repo-alpha", "recent event", "", recent_ts),
    )
    await cache._db.commit()

    counts = await cache.run_maintenance({"repo-alpha"})
    assert counts["old_events"] == 1

    # Recent seen event still there
    async with cache._db.execute("SELECT COUNT(*) FROM events") as cur:
        assert (await cur.fetchone())[0] == 1


async def test_maintenance_purges_orphaned_repo_data(cache: Cache):
    """Data for repos not in active set is purged."""
    await cache.store_mrs("repo-alpha", [make_mr(1)])
    await cache.store_mrs("repo-removed", [make_mr(2, repo_name="repo-removed")])
    await cache.store_pipelines("repo-removed", [make_pipeline(10, repo_name="repo-removed")])
    await cache.store_vulns("repo-removed", [make_vuln(200, repo_name="repo-removed")])
    await cache.store_issues("repo-removed", [make_issue(1, repo_name="repo-removed")])
    await cache.store_pull_status("repo-removed", "clean")
    await cache.store_events([make_event(repo_name="repo-removed")])

    counts = await cache.run_maintenance({"repo-alpha"})

    # repo-alpha data survives
    assert len(await cache.get_mrs("repo-alpha")) == 1
    # repo-removed data is gone
    assert len(await cache.get_mrs("repo-removed")) == 0
    assert len(await cache.get_pipelines("repo-removed")) == 0
    assert len(await cache.get_vulns("repo-removed")) == 0
    assert len(await cache.get_issues("repo-removed")) == 0
    assert await cache.get_pull_status("repo-removed") is None
    assert counts["orphan_merge_requests"] == 1


async def test_maintenance_no_op_when_clean(cache: Cache):
    """Maintenance with no stale data returns all-zero counts."""
    await cache.store_mrs("repo-alpha", [make_mr(1)])
    counts = await cache.run_maintenance({"repo-alpha"})
    assert counts.get("old_events", 0) == 0
    assert len(await cache.get_mrs("repo-alpha")) == 1


async def test_store_vulns_with_string_id(cache):
    """Verify cache handles string vulnerability IDs (GitHub format)."""
    from project_hub.core.models import Vulnerability
    vuln = Vulnerability(
        id="dependabot-42", name="CVE-2024-1234", severity="high",
        state="detected", source="DEPENDENCY_SCANNING", web_url="https://github.com/...",
        repo_name="my-repo",
    )
    await cache.store_vulns("my-repo", [vuln])
    loaded = await cache.get_vulns("my-repo")
    assert len(loaded) == 1
    assert loaded[0].id == "dependabot-42"
    assert loaded[0].name == "CVE-2024-1234"
