from __future__ import annotations

from project_hub.core.events import diff_mrs, diff_pipelines, diff_vulns, diff_issues, filter_events
from project_hub.core.models import MR, Event, Pipeline, Vulnerability, EventType, Issue

from datetime import datetime


def _mr(iid=1, state="opened", notes=0, approved=False, sha="abc123"):
    return MR(id=iid, iid=iid, title="MR", author="alice", state=state,
              source_branch="feat", target_branch="main", web_url="http://x",
              user_notes_count=notes, approved=approved,
              created_at="2026-01-01", updated_at="2026-01-01", repo_name="repo-a",
              sha=sha)


def _pipeline(id=1, status="success"):
    return Pipeline(id=id, status=status, ref="main", web_url="http://x",
                    created_at="2026-01-01", repo_name="repo-a")


def _vuln(id=1):
    return Vulnerability(id=id, name="CVE-X", severity="high", state="detected",
                         source="sast", web_url="http://x", repo_name="repo-a")


# --- MR tests ---

def test_diff_mrs_new_comment():
    old = [_mr(iid=1, notes=2)]
    new = [_mr(iid=1, notes=5)]
    events = diff_mrs(old, new, "repo-a")
    assert len(events) == 1
    assert events[0].type == EventType.MR_NEW_COMMENT


def test_diff_mrs_merged():
    old = [_mr(iid=1, state="opened")]
    new = [_mr(iid=1, state="merged")]
    events = diff_mrs(old, new, "repo-a")
    assert len(events) == 1
    assert events[0].type == EventType.MR_MERGED


def test_diff_mrs_approved():
    old = [_mr(iid=1, approved=False)]
    new = [_mr(iid=1, approved=True)]
    events = diff_mrs(old, new, "repo-a")
    assert len(events) == 1
    assert events[0].type == EventType.MR_APPROVED


def test_diff_mrs_no_change():
    old = [_mr(iid=1, state="opened", notes=3, approved=False)]
    new = [_mr(iid=1, state="opened", notes=3, approved=False)]
    events = diff_mrs(old, new, "repo-a")
    assert events == []


def test_diff_mrs_new_mr_ignored():
    old = []
    new = [_mr(iid=42)]
    events = diff_mrs(old, new, "repo-a")
    assert events == []


def test_diff_mrs_new_push():
    old = [_mr(iid=1, sha="aaa")]
    new = [_mr(iid=1, sha="bbb")]
    events = diff_mrs(old, new, "repo-a")
    assert len(events) == 1
    assert events[0].type == EventType.MR_NEW_PUSH
    assert "bbb" in events[0].summary


def test_diff_mrs_new_push_same_sha():
    old = [_mr(iid=1, sha="aaa")]
    new = [_mr(iid=1, sha="aaa")]
    events = diff_mrs(old, new, "repo-a")
    assert events == []


# --- Pipeline tests ---

def test_diff_pipelines_failed():
    old = [_pipeline(id=1, status="success")]
    new = [_pipeline(id=1, status="failed")]
    events = diff_pipelines(old, new, "repo-a")
    assert len(events) == 1
    assert events[0].type == EventType.PIPELINE_FAILED


def test_diff_pipelines_fixed():
    old = [_pipeline(id=1, status="failed")]
    new = [_pipeline(id=1, status="success")]
    events = diff_pipelines(old, new, "repo-a")
    assert len(events) == 1
    assert events[0].type == EventType.PIPELINE_FIXED


def test_diff_pipelines_no_change():
    old = [_pipeline(id=1, status="failed")]
    new = [_pipeline(id=1, status="failed")]
    events = diff_pipelines(old, new, "repo-a")
    assert events == []


def test_diff_pipelines_new_failed():
    old = []
    new = [_pipeline(id=1, status="failed")]
    events = diff_pipelines(old, new, "repo-a")
    assert len(events) == 1
    assert events[0].type == EventType.PIPELINE_FAILED


def test_diff_pipelines_new_success():
    old = []
    new = [_pipeline(id=1, status="success")]
    events = diff_pipelines(old, new, "repo-a")
    assert events == []


# --- Vulnerability tests ---

def test_diff_vulns_new():
    old = []
    new = [_vuln(id=1)]
    events = diff_vulns(old, new, "repo-a")
    assert len(events) == 1
    assert events[0].type == EventType.VULN_NEW


def test_diff_vulns_resolved():
    old = [_vuln(id=1)]
    new = []
    events = diff_vulns(old, new, "repo-a")
    assert len(events) == 1
    assert events[0].type == EventType.VULN_RESOLVED


def test_diff_vulns_no_change():
    old = [_vuln(id=1), _vuln(id=2)]
    new = [_vuln(id=1), _vuln(id=2)]
    events = diff_vulns(old, new, "repo-a")
    assert events == []


def test_diff_vulns_id_rotation_same_name_no_events():
    """When a pipeline re-scans and a vuln gets a new ID but the same name, no events."""
    old = [Vulnerability(id=100, name="CVE-2024-001", severity="high", state="detected",
                         source="sast", web_url="http://x", repo_name="repo-a")]
    new = [Vulnerability(id=200, name="CVE-2024-001", severity="high", state="detected",
                         source="sast", web_url="http://x", repo_name="repo-a")]
    events = diff_vulns(old, new, "repo-a")
    assert events == []


def test_diff_vulns_id_rotation_mixed():
    """Rotated name cancelled, genuinely new/resolved still emit events."""
    old = [Vulnerability(id=100, name="CVE-ROTATED", severity="high", state="detected",
                         source="sast", web_url="http://x", repo_name="repo-a"),
           Vulnerability(id=101, name="CVE-GONE", severity="medium", state="detected",
                         source="sast", web_url="http://x", repo_name="repo-a")]
    new = [Vulnerability(id=200, name="CVE-ROTATED", severity="high", state="detected",
                         source="sast", web_url="http://x", repo_name="repo-a"),
           Vulnerability(id=201, name="CVE-BRAND-NEW", severity="critical", state="detected",
                         source="sast", web_url="http://x", repo_name="repo-a")]
    events = diff_vulns(old, new, "repo-a")
    types = {e.type for e in events}
    assert types == {EventType.VULN_NEW, EventType.VULN_RESOLVED}
    assert any("CVE-BRAND-NEW" in e.summary for e in events)
    assert any("CVE-GONE" in e.summary for e in events)
    assert not any("CVE-ROTATED" in e.summary for e in events)


def _issue(iid=1, state="opened", notes=0, assignees=None):
    return Issue(id=iid * 100, iid=iid, title="Issue", author="alice", state=state,
                 web_url="http://x", user_notes_count=notes,
                 created_at="2026-01-01", updated_at="2026-01-01", repo_name="repo-a",
                 assignees=assignees or ["alice"], participants=["alice", "bob"])


# --- Issue tests ---

def test_diff_issues_opened():
    old = [_issue(iid=1)]
    new = [_issue(iid=1), _issue(iid=2)]
    events = diff_issues(old, new, "repo-a")
    assert len(events) == 1
    assert events[0].type == EventType.ISSUE_OPENED


def test_diff_issues_opened_skipped_on_empty_cache():
    old = []
    new = [_issue(iid=1), _issue(iid=2)]
    events = diff_issues(old, new, "repo-a")
    assert events == []


def test_diff_issues_closed():
    old = [_issue(iid=1, state="opened")]
    new = [_issue(iid=1, state="closed")]
    events = diff_issues(old, new, "repo-a")
    assert len(events) == 1
    assert events[0].type == EventType.ISSUE_CLOSED


def test_diff_issues_reopened():
    old = [_issue(iid=1, state="closed")]
    new = [_issue(iid=1, state="opened")]
    events = diff_issues(old, new, "repo-a")
    assert len(events) == 1
    assert events[0].type == EventType.ISSUE_REOPENED


def test_diff_issues_new_comment():
    old = [_issue(iid=1, notes=2)]
    new = [_issue(iid=1, notes=5)]
    events = diff_issues(old, new, "repo-a")
    assert len(events) == 1
    assert events[0].type == EventType.ISSUE_NEW_COMMENT


def test_diff_issues_assigned():
    old = [_issue(iid=1, assignees=["alice"])]
    new = [_issue(iid=1, assignees=["alice", "bob"])]
    events = diff_issues(old, new, "repo-a")
    assert len(events) == 1
    assert events[0].type == EventType.ISSUE_ASSIGNED


def test_diff_issues_no_change():
    old = [_issue(iid=1, state="opened", notes=3, assignees=["alice"])]
    new = [_issue(iid=1, state="opened", notes=3, assignees=["alice"])]
    events = diff_issues(old, new, "repo-a")
    assert events == []


# --- filter_events helpers ---

def _make_event(type: EventType, summary: str = "") -> Event:
    return Event(id=None, type=type, repo_name="repo-a", summary=summary,
                 detail="", created_at=datetime.now())


def _mr_with(iid=1, author="alice", source_branch="feat"):
    return MR(id=iid, iid=iid, title="MR", author=author, state="opened",
              source_branch=source_branch, target_branch="main", web_url="http://x",
              user_notes_count=0, approved=False,
              created_at="2026-01-01", updated_at="2026-01-01", repo_name="repo-a")


# --- filter_events: MR mode tests ---

def test_filter_events_mrs_none():
    events = [
        _make_event(EventType.MR_NEW_COMMENT, "MR !1 'fix': 2 new comment(s)"),
        _make_event(EventType.MR_APPROVED, "MR !2 'feat' approved"),
        _make_event(EventType.PIPELINE_FAILED, "Pipeline #10 on 'main' failed"),
    ]
    result = filter_events(events, mrs_mode="none", pipelines_mode="all",
                           vulns_mode="all", username="alice", mrs=[], pipelines=[])
    assert len(result) == 1
    assert result[0].type == EventType.PIPELINE_FAILED


def test_filter_events_mrs_mine():
    mrs = [_mr_with(iid=1, author="alice"), _mr_with(iid=2, author="bob")]
    events = [
        _make_event(EventType.MR_NEW_COMMENT, "MR !1 'fix': 2 new comment(s)"),
        _make_event(EventType.MR_APPROVED, "MR !2 'feat' approved"),
    ]
    result = filter_events(events, mrs_mode="mine", pipelines_mode="all",
                           vulns_mode="all", username="alice", mrs=mrs, pipelines=[])
    assert len(result) == 1
    assert "!1" in result[0].summary


def test_filter_events_mrs_all():
    mrs = [_mr_with(iid=1, author="alice"), _mr_with(iid=2, author="bob")]
    events = [
        _make_event(EventType.MR_NEW_COMMENT, "MR !1 'fix': 2 new comment(s)"),
        _make_event(EventType.MR_APPROVED, "MR !2 'feat' approved"),
    ]
    result = filter_events(events, mrs_mode="all", pipelines_mode="all",
                           vulns_mode="all", username="alice", mrs=mrs, pipelines=[])
    assert len(result) == 2


# --- filter_events: pipeline mode tests ---

def test_filter_events_pipelines_mine():
    mrs = [_mr_with(iid=1, author="alice", source_branch="feat/mine")]
    events = [
        _make_event(EventType.PIPELINE_FAILED, "Pipeline #10 on 'feat/mine' failed"),
        _make_event(EventType.PIPELINE_FAILED, "Pipeline #11 on 'feat/other' failed"),
    ]
    result = filter_events(events, mrs_mode="all", pipelines_mode="mine",
                           vulns_mode="all", username="alice", mrs=mrs, pipelines=[])
    assert len(result) == 1
    assert "feat/mine" in result[0].summary


def test_filter_events_pipelines_mine_default_branch():
    """Pipelines on main/master are always kept in 'mine' mode."""
    events = [
        _make_event(EventType.PIPELINE_FAILED, "Pipeline #10 on 'main' failed"),
        _make_event(EventType.PIPELINE_FAILED, "Pipeline #11 on 'master' failed"),
        _make_event(EventType.PIPELINE_FAILED, "Pipeline #12 on 'develop' failed"),
    ]
    result = filter_events(events, mrs_mode="all", pipelines_mode="mine",
                           vulns_mode="all", username="alice", mrs=[], pipelines=[])
    assert len(result) == 2
    refs = [e.summary for e in result]
    assert any("main" in s for s in refs)
    assert any("master" in s for s in refs)


def test_filter_events_pipelines_none():
    events = [
        _make_event(EventType.PIPELINE_FAILED, "Pipeline #10 on 'main' failed"),
        _make_event(EventType.MR_MERGED, "MR !1 'fix' merged"),
    ]
    result = filter_events(events, mrs_mode="all", pipelines_mode="none",
                           vulns_mode="all", username="alice", mrs=[], pipelines=[])
    assert len(result) == 1
    assert result[0].type == EventType.MR_MERGED


# --- filter_events: vuln mode tests ---

def test_filter_events_vulns_none():
    events = [
        _make_event(EventType.VULN_NEW, "New vulnerability: CVE-X (high)"),
        _make_event(EventType.VULN_RESOLVED, "Vulnerability resolved: CVE-Y (medium)"),
        _make_event(EventType.MR_MERGED, "MR !1 'fix' merged"),
    ]
    result = filter_events(events, mrs_mode="all", pipelines_mode="all",
                           vulns_mode="none", username="alice", mrs=[], pipelines=[])
    assert len(result) == 1
    assert result[0].type == EventType.MR_MERGED


# --- filter_events: auth always kept ---

def test_filter_events_auth_always_kept():
    events = [
        _make_event(EventType.AUTH_EXPIRED, "Authentication failed for gitlab.com"),
        _make_event(EventType.MR_NEW_COMMENT, "MR !1 'fix': 1 new comment(s)"),
        _make_event(EventType.PIPELINE_FAILED, "Pipeline #10 on 'main' failed"),
        _make_event(EventType.VULN_NEW, "New vulnerability: CVE-X (high)"),
    ]
    result = filter_events(events, mrs_mode="none", pipelines_mode="none",
                           vulns_mode="none", username="alice", mrs=[], pipelines=[])
    assert len(result) == 1
    assert result[0].type == EventType.AUTH_EXPIRED


# --- filter_events: issue mode tests ---

def _issue_with(iid=1, author="alice"):
    return Issue(id=iid * 100, iid=iid, title="Issue", author=author, state="opened",
                 web_url="http://x", user_notes_count=0,
                 created_at="2026-01-01", updated_at="2026-01-01", repo_name="repo-a",
                 assignees=[author], participants=[author])


def test_filter_events_issues_none():
    events = [
        _make_event(EventType.ISSUE_NEW_COMMENT, "Issue #1 'fix': 2 new comment(s)"),
        _make_event(EventType.MR_MERGED, "MR !1 'fix' merged"),
    ]
    result = filter_events(events, mrs_mode="all", pipelines_mode="all",
                           vulns_mode="all", username="alice", mrs=[], pipelines=[],
                           issues_mode="none", issues=[])
    assert len(result) == 1
    assert result[0].type == EventType.MR_MERGED


def test_filter_events_issues_mine():
    issues = [_issue_with(iid=1, author="alice"), _issue_with(iid=2, author="bob")]
    events = [
        _make_event(EventType.ISSUE_NEW_COMMENT, "Issue #1 'fix': 2 new comment(s)"),
        _make_event(EventType.ISSUE_CLOSED, "Issue #2 'feat' closed"),
    ]
    result = filter_events(events, mrs_mode="all", pipelines_mode="all",
                           vulns_mode="all", username="alice", mrs=[], pipelines=[],
                           issues_mode="mine", issues=issues)
    assert len(result) == 1
    assert "#1" in result[0].summary


def test_filter_events_issues_all():
    issues = [_issue_with(iid=1, author="alice"), _issue_with(iid=2, author="bob")]
    events = [
        _make_event(EventType.ISSUE_NEW_COMMENT, "Issue #1 'fix': 2 new comment(s)"),
        _make_event(EventType.ISSUE_CLOSED, "Issue #2 'feat' closed"),
    ]
    result = filter_events(events, mrs_mode="all", pipelines_mode="all",
                           vulns_mode="all", username="alice", mrs=[], pipelines=[],
                           issues_mode="all", issues=issues)
    assert len(result) == 2
