import pytest
from pathlib import Path
from datetime import datetime

from project_hub.core.cache import Cache
from project_hub.core.models import MR, Pipeline, Vulnerability, Event, EventType


@pytest.fixture
async def cache(tmp_path):
    c = Cache(tmp_path / "cache.db")
    await c.initialize()
    yield c
    await c.close()


def make_mr(iid: int, repo_name: str = "repo-alpha") -> MR:
    return MR(
        id=iid * 100,
        iid=iid,
        title=f"MR {iid}",
        author="alice",
        state="opened",
        source_branch=f"feature/{iid}",
        target_branch="main",
        web_url=f"https://gitlab.com/mr/{iid}",
        user_notes_count=0,
        approved=False,
        created_at="2024-01-01T00:00:00Z",
        updated_at="2024-01-02T00:00:00Z",
        repo_name=repo_name,
    )


def make_pipeline(pid: int, repo_name: str = "repo-alpha") -> Pipeline:
    return Pipeline(
        id=pid,
        status="success",
        ref="main",
        web_url=f"https://gitlab.com/pipelines/{pid}",
        created_at="2024-01-01T00:00:00Z",
        repo_name=repo_name,
    )


def make_vuln(vid: int, repo_name: str = "repo-alpha") -> Vulnerability:
    return Vulnerability(
        id=vid,
        name=f"CVE-{vid}",
        severity="high",
        state="detected",
        source="sast",
        web_url=f"https://gitlab.com/vulns/{vid}",
        repo_name=repo_name,
    )


def make_event(etype: EventType = EventType.PIPELINE_FAILED, repo_name: str = "repo-alpha") -> Event:
    return Event(
        id=None,
        type=etype,
        repo_name=repo_name,
        summary="Pipeline failed on main",
        detail="Job test-unit exited with code 1",
        created_at=datetime(2024, 1, 1, 12, 0, 0),
    )


# --- Tests ---


async def test_schema_created(cache: Cache):
    tables = await cache.get_tables()
    assert set(tables) >= {"merge_requests", "pipelines", "vulnerabilities", "events", "cache_metadata"}


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}
    assert all(mr.repo_name == "repo-alpha" for mr in result)
    assert all(mr.author == "alice" for mr in result)


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_schema_version_mismatch(cache: Cache):
    # Corrupt the schema version
    await cache._execute("UPDATE cache_metadata SET value = '0' WHERE key = 'schema_version'")
    # Reinitialize — should drop and recreate tables
    await cache.initialize()
    tables = await cache.get_tables()
    assert set(tables) >= {"merge_requests", "pipelines", "vulnerabilities", "events", "cache_metadata"}
    # Schema version should now be correct
    async with cache._db.execute("SELECT value FROM cache_metadata WHERE key = 'schema_version'") as cur:
        row = await cur.fetchone()
    assert row is not None
    assert row[0] == "4"
