from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from datetime import timedelta

from clients.models import Client, ClientPortalAccess
from core.choices import BillingCycle, ServiceStatus, ServiceType
from services.models import Service
from technical.models import TechnicalAsset

from .models import MonitorCheck, MonitorIncident, WebsiteMonitor
from .services import ProbeResult, parse_expected_status_codes, run_monitor_check


class MonitoringServiceTests(TestCase):
    def setUp(self):
        self.client_obj = Client.objects.create(name="Cliente Monitor", email="cliente@example.test")
        self.service = Service.objects.create(
            client=self.client_obj,
            service_type=ServiceType.WEBSITE,
            name="Website Monitorizado",
            status=ServiceStatus.ACTIVE,
            billing_cycle=BillingCycle.NONE,
            is_recurring=False,
        )
        self.asset = TechnicalAsset.objects.create(
            service=self.service,
            asset_type=TechnicalAsset.AssetType.WEBSITE,
            label="Website cliente",
            domain_name="example.test",
            status=TechnicalAsset.Status.UNKNOWN,
        )
        self.monitor = WebsiteMonitor.objects.create(
            technical_asset=self.asset,
            name="Monitor principal",
            url="https://example.test",
            alert_after_failures=2,
            recover_after_successes=1,
            notify_internal=False,
            notify_client=False,
        )

    def test_expected_status_parser(self):
        self.assertEqual(parse_expected_status_codes("200,204,300-302"), {200, 204, 300, 301, 302})
        with self.assertRaises(ValueError):
            parse_expected_status_codes("700")

    def test_incident_opens_after_threshold_and_resolves(self):
        down = lambda monitor: ProbeResult(
            result=MonitorCheck.Result.DOWN,
            http_status=503,
            response_time_ms=12000,
            error_type=MonitorCheck.ErrorType.HTTP,
            error_message="HTTP 503",
        )
        first = run_monitor_check(self.monitor, probe=down)
        self.monitor.refresh_from_db()
        self.assertEqual(first.result, MonitorCheck.Result.DOWN)
        self.assertEqual(self.monitor.status, WebsiteMonitor.Status.DEGRADED)
        self.assertFalse(MonitorIncident.objects.exists())

        run_monitor_check(self.monitor, probe=down)
        self.monitor.refresh_from_db()
        incident = MonitorIncident.objects.get()
        self.assertEqual(self.monitor.status, WebsiteMonitor.Status.DOWN)
        self.assertEqual(incident.status, MonitorIncident.Status.OPEN)
        self.assertIsNotNone(incident.internal_task_id)

        up = lambda monitor: ProbeResult(
            result=MonitorCheck.Result.UP,
            http_status=200,
            response_time_ms=350,
            final_url=monitor.url,
            ssl_valid=True,
            ssl_days_remaining=60,
        )
        run_monitor_check(self.monitor, probe=up)
        self.monitor.refresh_from_db()
        incident.refresh_from_db()
        self.assertEqual(self.monitor.status, WebsiteMonitor.Status.UP)
        self.assertEqual(incident.status, MonitorIncident.Status.RESOLVED)
        self.assertIsNotNone(incident.resolved_at)

    def test_maintenance_creates_non_failure_check(self):
        self.monitor.maintenance_until = timezone.now() + timedelta(hours=1)
        self.monitor.save()
        check = run_monitor_check(self.monitor)
        self.assertEqual(check.result, MonitorCheck.Result.MAINTENANCE)
        self.assertEqual(self.monitor.incidents.count(), 0)


class MonitoringViewTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.staff = get_user_model().objects.create_user("staff", password="TestPass123!", is_staff=True)
        cls.customer = get_user_model().objects.create_user("customer", password="TestPass123!")
        cls.other_customer = get_user_model().objects.create_user("other", password="TestPass123!")
        cls.client_a = Client.objects.create(name="Cliente A", email="a@example.test", portal_access_enabled=True)
        cls.client_b = Client.objects.create(name="Cliente B", email="b@example.test", portal_access_enabled=True)
        ClientPortalAccess.objects.create(client=cls.client_a, user=cls.customer, accepted_at=timezone.now())
        ClientPortalAccess.objects.create(client=cls.client_b, user=cls.other_customer, accepted_at=timezone.now())
        for client, domain, status in [
            (cls.client_a, "a.example.test", WebsiteMonitor.Status.UP),
            (cls.client_b, "b.example.test", WebsiteMonitor.Status.DOWN),
        ]:
            service = Service.objects.create(
                client=client, service_type=ServiceType.WEBSITE, name=f"Site {client.name}",
                status=ServiceStatus.ACTIVE, billing_cycle=BillingCycle.NONE, is_recurring=False,
            )
            asset = TechnicalAsset.objects.create(
                service=service, asset_type=TechnicalAsset.AssetType.WEBSITE,
                label=domain, domain_name=domain,
            )
            WebsiteMonitor.objects.create(
                technical_asset=asset, name=domain, url=f"https://{domain}", status=status,
                notify_internal=False, notify_client=False,
            )

    def test_staff_monitoring_center(self):
        self.client.force_login(self.staff)
        response = self.client.get(reverse("monitoring_center"))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "Centro de monitorização")

    def test_client_sees_only_own_monitors(self):
        self.client.force_login(self.customer)
        response = self.client.get(reverse("client_portal:monitoring"))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "a.example.test")
        self.assertNotContains(response, "b.example.test")
