"""Tests for model methods not covered by other tests."""

from datetime import timedelta

import pytest
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.utils import timezone

from notices.models import (
    EventNotification,
    Impact,
    Maintenance,
    NotificationTemplate,
    Outage,
    PreparedNotification,
    SentNotification,
)


@pytest.mark.django_db
class TestMaintenanceTimezoneMethods:
    """Tests for Maintenance timezone conversion methods."""

    def test_get_start_in_original_tz_with_valid_timezone(self, provider):
        """Should convert start to original timezone."""
        maintenance = Maintenance.objects.create(
            name="TZ Test",
            provider=provider,
            start=timezone.now(),
            end=timezone.now() + timedelta(hours=4),
            status="CONFIRMED",
            original_timezone="America/New_York",
        )
        result = maintenance.get_start_in_original_tz()
        assert result.tzinfo is not None
        assert "America/New_York" in str(result.tzinfo) or result != maintenance.start

    def test_get_start_in_original_tz_without_timezone(self, provider):
        """Should return original start when no timezone set."""
        maintenance = Maintenance.objects.create(
            name="No TZ",
            provider=provider,
            start=timezone.now(),
            end=timezone.now() + timedelta(hours=4),
            status="CONFIRMED",
        )
        result = maintenance.get_start_in_original_tz()
        assert result == maintenance.start

    def test_get_start_in_original_tz_with_invalid_timezone(self, provider):
        """Should return original start when timezone is invalid."""
        maintenance = Maintenance.objects.create(
            name="Invalid TZ",
            provider=provider,
            start=timezone.now(),
            end=timezone.now() + timedelta(hours=4),
            status="CONFIRMED",
            original_timezone="Invalid/Timezone",
        )
        result = maintenance.get_start_in_original_tz()
        assert result == maintenance.start

    def test_get_end_in_original_tz_with_valid_timezone(self, provider):
        """Should convert end to original timezone."""
        maintenance = Maintenance.objects.create(
            name="TZ Test",
            provider=provider,
            start=timezone.now(),
            end=timezone.now() + timedelta(hours=4),
            status="CONFIRMED",
            original_timezone="Europe/London",
        )
        result = maintenance.get_end_in_original_tz()
        assert result is not None

    def test_get_end_in_original_tz_without_timezone(self, provider):
        """Should return original end when no timezone set."""
        maintenance = Maintenance.objects.create(
            name="No TZ",
            provider=provider,
            start=timezone.now(),
            end=timezone.now() + timedelta(hours=4),
            status="CONFIRMED",
        )
        result = maintenance.get_end_in_original_tz()
        assert result == maintenance.end

    def test_get_end_in_original_tz_with_invalid_timezone(self, provider):
        """Should return original end when timezone is invalid."""
        maintenance = Maintenance.objects.create(
            name="Invalid TZ",
            provider=provider,
            start=timezone.now(),
            end=timezone.now() + timedelta(hours=4),
            status="CONFIRMED",
            original_timezone="Not/A/Zone",
        )
        result = maintenance.get_end_in_original_tz()
        assert result == maintenance.end

    def test_has_timezone_difference_returns_true(self, provider):
        """Should return True when timezones differ."""
        maintenance = Maintenance.objects.create(
            name="TZ Diff",
            provider=provider,
            start=timezone.now(),
            end=timezone.now() + timedelta(hours=4),
            status="CONFIRMED",
            original_timezone="Pacific/Auckland",
        )
        # Result depends on current server timezone
        result = maintenance.has_timezone_difference()
        assert isinstance(result, bool)

    def test_has_timezone_difference_returns_false_without_tz(self, provider):
        """Should return False when no original timezone."""
        maintenance = Maintenance.objects.create(
            name="No TZ",
            provider=provider,
            start=timezone.now(),
            end=timezone.now() + timedelta(hours=4),
            status="CONFIRMED",
        )
        assert maintenance.has_timezone_difference() is False

    def test_has_timezone_difference_with_invalid_tz(self, provider):
        """Should return False when timezone is invalid."""
        maintenance = Maintenance.objects.create(
            name="Invalid TZ",
            provider=provider,
            start=timezone.now(),
            end=timezone.now() + timedelta(hours=4),
            status="CONFIRMED",
            original_timezone="Invalid/Zone",
        )
        assert maintenance.has_timezone_difference() is False


@pytest.mark.django_db
class TestOutageTimezoneMethods:
    """Tests for Outage timezone conversion methods."""

    def test_get_start_in_original_tz(self, provider):
        """Should convert start to original timezone."""
        outage = Outage.objects.create(
            name="TZ Test",
            provider=provider,
            status="REPORTED",
            original_timezone="America/Los_Angeles",
        )
        result = outage.get_start_in_original_tz()
        assert result is not None

    def test_get_end_in_original_tz_with_end(self, provider):
        """Should convert end to original timezone when set."""
        outage = Outage.objects.create(
            name="TZ Test",
            provider=provider,
            end=timezone.now() + timedelta(hours=2),
            status="RESOLVED",
            original_timezone="Asia/Tokyo",
        )
        result = outage.get_end_in_original_tz()
        assert result is not None

    def test_get_end_in_original_tz_without_end(self, provider):
        """Should return None when no end time."""
        outage = Outage.objects.create(
            name="No End",
            provider=provider,
            status="INVESTIGATING",
            original_timezone="Asia/Tokyo",
        )
        result = outage.get_end_in_original_tz()
        assert result is None

    def test_get_estimated_time_to_repair_in_original_tz(self, provider):
        """Should convert ETR to original timezone."""
        outage = Outage.objects.create(
            name="ETR Test",
            provider=provider,
            status="INVESTIGATING",
            estimated_time_to_repair=timezone.now() + timedelta(hours=4),
            original_timezone="Europe/Paris",
        )
        result = outage.get_estimated_time_to_repair_in_original_tz()
        assert result is not None

    def test_get_estimated_time_to_repair_without_etr(self, provider):
        """Should return None when no ETR."""
        outage = Outage.objects.create(
            name="No ETR",
            provider=provider,
            status="INVESTIGATING",
        )
        result = outage.get_estimated_time_to_repair_in_original_tz()
        assert result is None

    def test_get_reported_at_in_original_tz(self, provider):
        """Should convert reported_at to original timezone."""
        outage = Outage.objects.create(
            name="Reported TZ",
            provider=provider,
            status="REPORTED",
            original_timezone="Australia/Sydney",
        )
        result = outage.get_reported_at_in_original_tz()
        assert result is not None

    def test_has_timezone_difference(self, provider):
        """Should detect timezone differences."""
        outage = Outage.objects.create(
            name="TZ Diff",
            provider=provider,
            status="REPORTED",
            original_timezone="Pacific/Fiji",
        )
        result = outage.has_timezone_difference()
        assert isinstance(result, bool)

    def test_has_timezone_difference_without_tz(self, provider):
        """Should return False without original timezone."""
        outage = Outage.objects.create(
            name="No TZ",
            provider=provider,
            status="REPORTED",
        )
        assert outage.has_timezone_difference() is False

    def test_outage_str(self, provider):
        """Should return name as string representation."""
        outage = Outage.objects.create(
            name="Test Outage Name",
            provider=provider,
            status="REPORTED",
        )
        assert str(outage) == "Test Outage Name"

    def test_get_status_color(self, provider):
        """Should return color for status."""
        outage = Outage.objects.create(
            name="Color Test",
            provider=provider,
            status="REPORTED",
        )
        color = outage.get_status_color()
        assert color is not None


@pytest.mark.django_db
class TestImpactMethods:
    """Tests for Impact model methods."""

    def test_str_representation(self, circuit, provider):
        """Should return formatted string."""
        maintenance = Maintenance.objects.create(
            name="MAINT-001",
            provider=provider,
            start=timezone.now(),
            end=timezone.now() + timedelta(hours=4),
            status="CONFIRMED",
        )
        ct = ContentType.objects.get_for_model(circuit)
        impact = Impact.objects.create(
            event_content_type=ContentType.objects.get_for_model(maintenance),
            event_object_id=maintenance.pk,
            target_content_type=ct,
            target_object_id=circuit.pk,
            impact="OUTAGE",
        )
        assert "MAINT-001" in str(impact)

    def test_str_with_missing_event(self, circuit):
        """Should handle missing event gracefully."""
        ct = ContentType.objects.get_for_model(circuit)
        maint_ct = ContentType.objects.get(app_label="notices", model="maintenance")
        impact = Impact(
            event_content_type=maint_ct,
            event_object_id=99999,  # Non-existent
            target_content_type=ct,
            target_object_id=circuit.pk,
            impact="OUTAGE",
        )
        result = str(impact)
        assert "Unknown" in result or result is not None

    def test_get_absolute_url_with_event(self, circuit, provider):
        """Should return event URL when event exists."""
        maintenance = Maintenance.objects.create(
            name="URL Test",
            provider=provider,
            start=timezone.now(),
            end=timezone.now() + timedelta(hours=4),
            status="CONFIRMED",
        )
        ct = ContentType.objects.get_for_model(circuit)
        impact = Impact.objects.create(
            event_content_type=ContentType.objects.get_for_model(maintenance),
            event_object_id=maintenance.pk,
            target_content_type=ct,
            target_object_id=circuit.pk,
        )
        url = impact.get_absolute_url()
        assert "maintenance" in url

    def test_get_impact_color(self, circuit, provider):
        """Should return color for impact level."""
        maintenance = Maintenance.objects.create(
            name="Color Test",
            provider=provider,
            start=timezone.now(),
            end=timezone.now() + timedelta(hours=4),
            status="CONFIRMED",
        )
        ct = ContentType.objects.get_for_model(circuit)
        impact = Impact.objects.create(
            event_content_type=ContentType.objects.get_for_model(maintenance),
            event_object_id=maintenance.pk,
            target_content_type=ct,
            target_object_id=circuit.pk,
            impact="OUTAGE",
        )
        color = impact.get_impact_color()
        assert color is not None

    def test_to_objectchange(self, circuit, provider):
        """Should set related_object to event."""
        maintenance = Maintenance.objects.create(
            name="Change Test",
            provider=provider,
            start=timezone.now(),
            end=timezone.now() + timedelta(hours=4),
            status="CONFIRMED",
        )
        ct = ContentType.objects.get_for_model(circuit)
        impact = Impact.objects.create(
            event_content_type=ContentType.objects.get_for_model(maintenance),
            event_object_id=maintenance.pk,
            target_content_type=ct,
            target_object_id=circuit.pk,
        )
        from core.choices import ObjectChangeActionChoices

        objectchange = impact.to_objectchange(ObjectChangeActionChoices.ACTION_CREATE)
        assert objectchange.related_object == maintenance

    def test_clean_validates_completed_event_status(self, circuit, provider):
        """Should reject modifications to impacts on completed events."""
        # Create a completed maintenance
        maintenance = Maintenance.objects.create(
            name="Completed",
            provider=provider,
            start=timezone.now() - timedelta(days=2),
            end=timezone.now() - timedelta(days=1),
            status="COMPLETED",
        )
        ct = ContentType.objects.get_for_model(circuit)
        maint_ct = ContentType.objects.get_for_model(maintenance)
        impact = Impact(
            event_content_type=maint_ct,
            event_object_id=maintenance.pk,
            target_content_type=ct,
            target_object_id=circuit.pk,
            impact="OUTAGE",
        )
        with pytest.raises(ValidationError) as exc_info:
            impact.clean()
        assert "cannot alter" in str(exc_info.value).lower()

    def test_clean_validates_cancelled_event_status(self, circuit, provider):
        """Should reject modifications to impacts on cancelled events."""
        # Create a cancelled maintenance
        maintenance = Maintenance.objects.create(
            name="Cancelled",
            provider=provider,
            start=timezone.now() + timedelta(days=1),
            end=timezone.now() + timedelta(days=2),
            status="CANCELLED",
        )
        ct = ContentType.objects.get_for_model(circuit)
        maint_ct = ContentType.objects.get_for_model(maintenance)
        impact = Impact(
            event_content_type=maint_ct,
            event_object_id=maintenance.pk,
            target_content_type=ct,
            target_object_id=circuit.pk,
            impact="DEGRADED",
        )
        with pytest.raises(ValidationError) as exc_info:
            impact.clean()
        assert "cannot alter" in str(exc_info.value).lower()


@pytest.mark.django_db
class TestEventNotificationMethods:
    """Tests for EventNotification model methods."""

    def test_str_returns_subject(self, provider):
        """Should return subject as string representation."""
        maintenance = Maintenance.objects.create(
            name="Notif Test",
            provider=provider,
            start=timezone.now(),
            end=timezone.now() + timedelta(hours=4),
            status="CONFIRMED",
        )
        notification = EventNotification.objects.create(
            event_content_type=ContentType.objects.get_for_model(maintenance),
            event_object_id=maintenance.pk,
            email=b"raw email data",
            email_body="Email body text",
            subject="Test Subject Line",
            email_from="noc@example.com",
            email_received=timezone.now(),
        )
        assert str(notification) == "Test Subject Line"

    def test_get_absolute_url(self, provider):
        """Should return notification URL."""
        maintenance = Maintenance.objects.create(
            name="URL Test",
            provider=provider,
            start=timezone.now(),
            end=timezone.now() + timedelta(hours=4),
            status="CONFIRMED",
        )
        notification = EventNotification.objects.create(
            event_content_type=ContentType.objects.get_for_model(maintenance),
            event_object_id=maintenance.pk,
            email=b"data",
            email_body="body",
            subject="Subject",
            email_from="test@example.com",
            email_received=timezone.now(),
        )
        url = notification.get_absolute_url()
        assert "notification" in url
        assert str(notification.pk) in url

    def test_to_objectchange(self, provider):
        """Should set related_object to event."""
        maintenance = Maintenance.objects.create(
            name="Change Test",
            provider=provider,
            start=timezone.now(),
            end=timezone.now() + timedelta(hours=4),
            status="CONFIRMED",
        )
        notification = EventNotification.objects.create(
            event_content_type=ContentType.objects.get_for_model(maintenance),
            event_object_id=maintenance.pk,
            email=b"data",
            email_body="body",
            subject="Subject",
            email_from="test@example.com",
            email_received=timezone.now(),
        )
        from core.choices import ObjectChangeActionChoices

        objectchange = notification.to_objectchange(ObjectChangeActionChoices.ACTION_CREATE)
        assert objectchange.related_object == maintenance


@pytest.mark.django_db
class TestNotificationTemplateGetAbsoluteUrl:
    """Test NotificationTemplate.get_absolute_url."""

    def test_get_absolute_url(self):
        template = NotificationTemplate.objects.create(
            name="T1",
            slug="t1-url",
            event_type="maintenance",
            granularity="per_event",
            subject_template="S",
            body_template="B",
            body_format="text",
            weight=1000,
        )
        url = template.get_absolute_url()
        assert f"/notification-templates/{template.pk}/" in url


@pytest.mark.django_db
class TestPreparedNotificationMethods:
    """Test PreparedNotification methods."""

    def _create_prepared(self, **kwargs):
        template = NotificationTemplate.objects.create(
            name="T-Prep",
            slug=f"t-prep-{kwargs.get('subject', 'default')[:10].lower().replace(' ', '-')}",
            event_type="maintenance",
            granularity="per_event",
            subject_template="S",
            body_template="B",
            body_format="text",
            weight=1000,
        )
        defaults = {
            "template": template,
            "subject": "Test Subject for Prep",
            "body_text": "body",
        }
        defaults.update(kwargs)
        return PreparedNotification.objects.create(**defaults)

    def test_get_absolute_url(self):
        pn = self._create_prepared()
        url = pn.get_absolute_url()
        assert f"/prepared-notifications/{pn.pk}/" in url

    def test_event_type_name_with_event(self, provider):
        now = timezone.now()
        maint = Maintenance.objects.create(
            name="M1", summary="T", provider=provider, start=now, end=now + timedelta(hours=2), status="CONFIRMED"
        )
        maint_ct = ContentType.objects.get_for_model(Maintenance)
        pn = self._create_prepared(event_content_type=maint_ct, event_id=maint.pk)
        assert pn.event_type_name == "maintenance"

    def test_event_type_name_without_event(self):
        pn = self._create_prepared()
        assert pn.event_type_name is None


@pytest.mark.django_db
class TestSentNotificationGetAbsoluteUrl:
    """Test SentNotification.get_absolute_url."""

    def test_get_absolute_url(self):
        from notices.choices import PreparedNotificationStatusChoices

        template = NotificationTemplate.objects.create(
            name="T-Sent",
            slug="t-sent-url",
            event_type="maintenance",
            granularity="per_event",
            subject_template="S",
            body_template="B",
            body_format="text",
            weight=1000,
        )
        pn = PreparedNotification.objects.create(
            template=template,
            subject="Test",
            body_text="body",
            status=PreparedNotificationStatusChoices.SENT,
        )
        sent = SentNotification.objects.get(pk=pn.pk)
        url = sent.get_absolute_url()
        assert f"/sent-notifications/{sent.pk}/" in url
