"""Diff old vs new forge state to produce Event lists, with watch-mode filtering."""

from __future__ import annotations

import re
from datetime import datetime

from .models import MR, Event, EventType, Pipeline, Vulnerability

_MR_EVENT_TYPES = {
    EventType.MR_NEW_COMMENT,
    EventType.MR_APPROVED,
    EventType.MR_CHANGES_REQUESTED,
    EventType.MR_MERGED,
    EventType.MR_NEW_PUSH,
}

_PIPELINE_EVENT_TYPES = {
    EventType.PIPELINE_FAILED,
    EventType.PIPELINE_FIXED,
}

_VULN_EVENT_TYPES = {
    EventType.VULN_NEW,
    EventType.VULN_RESOLVED,
}

_DEFAULT_BRANCHES = {"main", "master"}


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


def diff_mrs(old: list[MR], new: list[MR], repo_name: str) -> list[Event]:
    """Produce events by comparing old and new MR lists for a repo."""
    old_by_iid = {mr.iid: mr for mr in old}
    events: list[Event] = []

    for mr in new:
        prev = old_by_iid.get(mr.iid)
        if prev is None:
            continue  # new MR, not a state change

        if mr.state == "merged" and prev.state != "merged":
            events.append(_event(EventType.MR_MERGED, repo_name,
                                 f"MR !{mr.iid} '{mr.title}' merged"))

        if mr.user_notes_count > prev.user_notes_count:
            added = mr.user_notes_count - prev.user_notes_count
            events.append(_event(EventType.MR_NEW_COMMENT, repo_name,
                                 f"MR !{mr.iid} '{mr.title}': {added} new comment(s)"))

        if mr.approved and not prev.approved:
            events.append(_event(EventType.MR_APPROVED, repo_name,
                                 f"MR !{mr.iid} '{mr.title}' approved"))

        if mr.sha and prev.sha and mr.sha != prev.sha:
            events.append(_event(EventType.MR_NEW_PUSH, repo_name,
                                 f"MR !{mr.iid} '{mr.title}': new push ({mr.sha[:8]})"))

    return events


def diff_pipelines(old: list[Pipeline], new: list[Pipeline], repo_name: str) -> list[Event]:
    """Produce events by comparing old and new pipeline lists for a repo."""
    old_by_id = {p.id: p for p in old}
    events: list[Event] = []

    for pipeline in new:
        prev = old_by_id.get(pipeline.id)

        if prev is None:
            if pipeline.status == "failed":
                events.append(_event(EventType.PIPELINE_FAILED, repo_name,
                                     f"Pipeline #{pipeline.id} on '{pipeline.ref}' failed"))
            continue

        if pipeline.status == "failed" and prev.status != "failed":
            events.append(_event(EventType.PIPELINE_FAILED, repo_name,
                                 f"Pipeline #{pipeline.id} on '{pipeline.ref}' failed"))

        elif pipeline.status == "success" and prev.status == "failed":
            events.append(_event(EventType.PIPELINE_FIXED, repo_name,
                                 f"Pipeline #{pipeline.id} on '{pipeline.ref}' fixed"))

    return events


def diff_vulns(old: list[Vulnerability], new: list[Vulnerability], repo_name: str) -> list[Event]:
    """Produce events by comparing old and new vulnerability lists for a repo."""
    old_ids = {v.id for v in old}
    new_ids = {v.id for v in new}
    new_by_id = {v.id: v for v in new}
    old_by_id = {v.id: v for v in old}
    events: list[Event] = []

    for vid in new_ids - old_ids:
        v = new_by_id[vid]
        events.append(_event(EventType.VULN_NEW, repo_name,
                             f"New vulnerability: {v.name} ({v.severity})"))

    for vid in old_ids - new_ids:
        v = old_by_id[vid]
        events.append(_event(EventType.VULN_RESOLVED, repo_name,
                             f"Vulnerability resolved: {v.name} ({v.severity})"))

    return events


def _extract_mr_iid(summary: str) -> int | None:
    """Extract MR iid from event summary like "MR !42 'title' ..."."""
    m = re.search(r"MR !(\d+)", summary)
    return int(m.group(1)) if m else None


def _extract_pipeline_ref(summary: str) -> str | None:
    """Extract pipeline ref from event summary like "Pipeline #123 on 'feat/x' ..."."""
    m = re.search(r"on '([^']+)'", summary)
    return m.group(1) if m else None


def _should_drop_mr(event: Event, mode: str, username: str, mr_by_iid: dict[int, MR]) -> bool:
    if mode == "none":
        return True
    if mode == "mine" and username:
        iid = _extract_mr_iid(event.summary)
        mr = mr_by_iid.get(iid) if iid is not None else None
        if mr and not mr.involves(username):
            return True
    return False


def _should_drop_pipeline(event: Event, mode: str, username: str, my_branches: set[str]) -> bool:
    if mode == "none":
        return True
    if mode == "mine" and username:
        ref = _extract_pipeline_ref(event.summary)
        if ref and ref not in my_branches:
            return True
    return False


def filter_events(
    events: list[Event],
    mrs_mode: str,
    pipelines_mode: str,
    vulns_mode: str,
    username: str,
    mrs: list[MR],
    pipelines: list[Pipeline],
) -> list[Event]:
    """Filter events based on watch mode config.

    Modes per category:
      "all"  - keep all events
      "mine" - keep only events related to the user
      "none" - drop all events of this category

    AUTH_EXPIRED events always pass through.
    """
    if mrs_mode == "all" and pipelines_mode == "all" and vulns_mode == "all":
        return events  # fast path

    mr_by_iid = {mr.iid: mr for mr in mrs}
    my_mrs = [m for m in mrs if m.involves(username)] if username else []
    my_branches = {m.source_branch for m in my_mrs}
    # Include refs/merge-requests/N/head refs for MR-triggered pipelines
    for m in my_mrs:
        my_branches.add(f"refs/merge-requests/{m.iid}/head")
    my_branches |= _DEFAULT_BRANCHES

    filtered: list[Event] = []
    for event in events:
        if event.type == EventType.AUTH_EXPIRED:
            filtered.append(event)
        elif event.type in _MR_EVENT_TYPES:
            if not _should_drop_mr(event, mrs_mode, username, mr_by_iid):
                filtered.append(event)
        elif event.type in _PIPELINE_EVENT_TYPES:
            if not _should_drop_pipeline(event, pipelines_mode, username, my_branches):
                filtered.append(event)
        elif event.type in _VULN_EVENT_TYPES:
            if vulns_mode != "none":
                filtered.append(event)
        else:
            filtered.append(event)

    return filtered
