"""Tests for RAID signal handlers."""

from __future__ import annotations

from unittest.mock import ANY, Mock, call, patch

import pytest
from django.test import override_settings

from firefighter.incidents.enums import IncidentStatus
from firefighter.incidents.models.incident_update import IncidentUpdate
from firefighter.raid.signals.incident_updated import (
    IMPACT_TO_JIRA_STATUS_MAP,
    incident_updated_close_ticket_when_mitigated_or_postmortem,
)


@pytest.mark.django_db
class TestIncidentUpdatedCloseJiraTicket:
    """Test that Jira tickets are closed when incidents reach terminal statuses."""

    @patch("firefighter.raid.signals.incident_updated.client.transition_issue_auto")
    def test_close_jira_ticket_when_status_changes_to_mitigated(
        self,
        mock_transition: Mock,
        incident_factory,
        user_factory,
        jira_ticket_factory,
        priority_factory,
    ) -> None:
        """Impact → Jira: MITIGATED transitions to Reporter validation for P3+."""
        user = user_factory()
        # Create P3 priority (no postmortem needed)
        p3_priority = priority_factory(value=3, name="P3", needs_postmortem=False)
        incident = incident_factory(created_by=user, priority=p3_priority)
        jira_ticket = jira_ticket_factory(incident=incident)
        incident.jira_ticket = jira_ticket

        incident_update = IncidentUpdate(
            incident=incident,
            status=IncidentStatus.MITIGATED,
            created_by=user,
        )

        # Call the signal handler
        incident_updated_close_ticket_when_mitigated_or_postmortem(
            sender="update_status",
            incident=incident,
            incident_update=incident_update,
            updated_fields=["_status"],
        )

        target = IMPACT_TO_JIRA_STATUS_MAP[IncidentStatus.MITIGATED]
        mock_transition.assert_called_once_with(jira_ticket.id, target, ANY)

    @override_settings(ENABLE_JIRA_POSTMORTEM=True)
    @patch("firefighter.raid.signals.incident_updated.client.transition_issue_auto")
    def test_do_not_close_jira_ticket_when_p1_mitigated(
        self,
        mock_transition: Mock,
        incident_factory,
        user_factory,
        jira_ticket_factory,
        priority_factory,
        environment_factory,
    ) -> None:
        """Test that Jira ticket is NOT closed when P1 incident status changes to MITIGATED.

        For P1/P2 incidents requiring postmortem, the ticket should stay open through
        MITIGATED and POST_MORTEM phases, closing only at CLOSED.
        """
        user = user_factory()
        # Create P1 priority (needs postmortem)
        p1_priority = priority_factory(value=1, name="P1", needs_postmortem=True)
        prd_env = environment_factory(value="PRD", name="Production")
        incident = incident_factory(
            created_by=user, priority=p1_priority, environment=prd_env
        )
        jira_ticket = jira_ticket_factory(incident=incident)
        incident.jira_ticket = jira_ticket

        incident_update = IncidentUpdate(
            incident=incident,
            status=IncidentStatus.MITIGATED,
            created_by=user,
        )

        # Call the signal handler
        incident_updated_close_ticket_when_mitigated_or_postmortem(
            sender="update_status",
            incident=incident,
            incident_update=incident_update,
            updated_fields=["_status"],
        )

        target = IMPACT_TO_JIRA_STATUS_MAP[IncidentStatus.MITIGATED]
        mock_transition.assert_called_once_with(jira_ticket.id, target, ANY)

    @patch("firefighter.raid.signals.incident_updated.client.transition_issue_auto")
    def test_do_not_close_jira_ticket_when_status_changes_to_postmortem(
        self, mock_transition: Mock, incident_factory, user_factory, jira_ticket_factory
    ) -> None:
        """Test that Jira ticket is NOT closed when incident status changes to POST_MORTEM.

        The ticket should remain open during the post-mortem phase and only close
        when the incident reaches CLOSED status.
        """
        user = user_factory()
        incident = incident_factory(created_by=user)
        jira_ticket = jira_ticket_factory(incident=incident)
        incident.jira_ticket = jira_ticket

        incident_update = IncidentUpdate(
            incident=incident,
            status=IncidentStatus.POST_MORTEM,
            created_by=user,
        )

        # Call the signal handler
        incident_updated_close_ticket_when_mitigated_or_postmortem(
            sender="update_status",
            incident=incident,
            incident_update=incident_update,
            updated_fields=["_status"],
        )

        target = IMPACT_TO_JIRA_STATUS_MAP[IncidentStatus.POST_MORTEM]
        mock_transition.assert_called_once_with(jira_ticket.id, target, ANY)

    @patch("firefighter.raid.signals.incident_updated.client.transition_issue_auto")
    def test_close_jira_ticket_when_status_changes_to_closed(
        self, mock_transition: Mock, incident_factory, user_factory, jira_ticket_factory
    ) -> None:
        """Test that Jira ticket is closed when incident status changes to CLOSED (direct close)."""
        user = user_factory()
        incident = incident_factory(created_by=user)
        jira_ticket = jira_ticket_factory(incident=incident)
        incident.jira_ticket = jira_ticket

        incident_update = IncidentUpdate(
            incident=incident,
            status=IncidentStatus.CLOSED,
            created_by=user,
        )

        # Call the signal handler
        incident_updated_close_ticket_when_mitigated_or_postmortem(
            sender="update_status",
            incident=incident,
            incident_update=incident_update,
            updated_fields=["_status"],
        )

        mock_transition.assert_called_once_with(jira_ticket.id, "Closed", ANY)

    @patch("firefighter.raid.signals.incident_updated.client.transition_issue_auto")
    def test_do_not_close_jira_ticket_when_status_not_terminal(
        self, mock_transition: Mock, incident_factory, user_factory, jira_ticket_factory
    ) -> None:
        """Test that Jira ticket is NOT closed for non-terminal statuses.

        INVESTIGATING (and MITIGATING) trigger a two-step Jira transition:
        Pending resolution → in progress (to avoid webhook bouncing Impact back to OPEN).
        """
        user = user_factory()
        incident = incident_factory(created_by=user)
        jira_ticket = jira_ticket_factory(incident=incident)
        incident.jira_ticket = jira_ticket

        incident_update = IncidentUpdate(
            incident=incident,
            status=IncidentStatus.INVESTIGATING,
            created_by=user,
        )

        # Call the signal handler
        incident_updated_close_ticket_when_mitigated_or_postmortem(
            sender="update_status",
            incident=incident,
            incident_update=incident_update,
            updated_fields=["_status"],
        )

        # Two-step transition: Pending resolution then in progress
        assert mock_transition.call_count == 2
        mock_transition.assert_has_calls(
            [
                call(jira_ticket.id, "Pending resolution", ANY),
                call(jira_ticket.id, "in progress", ANY),
            ]
        )

    @patch("firefighter.raid.signals.incident_updated.client.transition_issue_auto")
    def test_do_not_close_jira_ticket_when_status_not_updated(
        self, mock_transition: Mock, incident_factory, user_factory, jira_ticket_factory
    ) -> None:
        """Test that Jira ticket is NOT closed when _status is not in updated_fields."""
        user = user_factory()
        incident = incident_factory(created_by=user)
        jira_ticket = jira_ticket_factory(incident=incident)
        incident.jira_ticket = jira_ticket

        incident_update = IncidentUpdate(
            incident=incident,
            status=IncidentStatus.CLOSED,
            created_by=user,
        )

        # Call the signal handler with updated_fields that don't include _status
        incident_updated_close_ticket_when_mitigated_or_postmortem(
            sender="update_status",
            incident=incident,
            incident_update=incident_update,
            updated_fields=["priority_id"],  # Not _status
        )

        mock_transition.assert_not_called()

    @patch("firefighter.raid.signals.incident_updated.client.transition_issue_auto")
    @patch("firefighter.raid.signals.incident_updated.logger")
    def test_do_not_crash_when_jira_ticket_missing(
        self,
        mock_logger: Mock,
        mock_transition: Mock,
        incident_factory,
        user_factory,
    ) -> None:
        """Test that signal handler handles gracefully when Jira ticket is missing."""
        user = user_factory()
        incident = incident_factory(created_by=user)
        # No jira_ticket attached to incident

        incident_update = IncidentUpdate(
            incident=incident,
            status=IncidentStatus.CLOSED,
            created_by=user,
        )

        # Call the signal handler - should not crash
        incident_updated_close_ticket_when_mitigated_or_postmortem(
            sender="update_status",
            incident=incident,
            incident_update=incident_update,
            updated_fields=["_status"],
        )

        # Verify transition was NOT called
        mock_transition.assert_not_called()

        # Verify a warning was logged
        mock_logger.warning.assert_called_once()
