from datetime import timedelta
from decimal import Decimal
from tempfile import TemporaryDirectory

from django.contrib.auth import get_user_model
from django.core.files.base import ContentFile
from django.test import TestCase, override_settings
from django.urls import reverse
from django.utils import timezone

from billing.models import Payment, Renewal
from clients.models import Client, ClientPortalAccess
from core.choices import BillingCycle, PaymentMethod, ServiceStatus, ServiceType
from core.models import Document, PortalConfiguration
from notifications.models import NotificationLog
from services.models import Service
from support.models import SupportMessage, SupportTicket


class ClientPortalTests(TestCase):
    def setUp(self):
        self.temp_media = TemporaryDirectory()
        self.addCleanup(self.temp_media.cleanup)
        self.override = override_settings(MEDIA_ROOT=self.temp_media.name)
        self.override.enable()
        self.addCleanup(self.override.disable)

        user_model = get_user_model()
        self.user = user_model.objects.create_user(
            username="cliente.portal",
            email="cliente@example.com",
            password="Portal-Teste-2026!",
            first_name="Cliente",
        )
        self.other_user = user_model.objects.create_user(
            username="outro.portal",
            email="outro@example.com",
            password="Portal-Teste-2026!",
        )
        self.staff = user_model.objects.create_superuser(
            username="admin.portal",
            email="admin@kreate4web.com",
            password="Portal-Teste-2026!",
        )

        self.client_record = Client.objects.create(
            name="Cliente Portal",
            email="cliente@example.com",
            portal_access_enabled=True,
        )
        self.other_client = Client.objects.create(
            name="Outro Cliente",
            email="outro@example.com",
            portal_access_enabled=True,
        )
        self.access = ClientPortalAccess.objects.create(
            client=self.client_record,
            user=self.user,
            role=ClientPortalAccess.Role.OWNER,
        )
        ClientPortalAccess.objects.create(
            client=self.other_client,
            user=self.other_user,
            role=ClientPortalAccess.Role.OWNER,
        )

        self.service = Service.objects.create(
            client=self.client_record,
            service_type=ServiceType.HOSTING,
            name="Alojamento Cliente",
            status=ServiceStatus.ACTIVE,
            next_renewal_date=timezone.localdate() + timedelta(days=30),
            billing_cycle=BillingCycle.ANNUAL,
            sale_price=Decimal("100.00"),
        )
        self.other_service = Service.objects.create(
            client=self.other_client,
            service_type=ServiceType.DOMAIN,
            name="dominio-outro.pt",
            status=ServiceStatus.ACTIVE,
            next_renewal_date=timezone.localdate() + timedelta(days=30),
            billing_cycle=BillingCycle.ANNUAL,
            sale_price=Decimal("25.00"),
        )
        self.renewal = Renewal.objects.create(
            service=self.service,
            due_date=self.service.next_renewal_date,
            sale_amount=self.service.sale_price,
        )
        self.payment = Payment.objects.create(
            renewal=self.renewal,
            amount=Decimal("100.00"),
            method=PaymentMethod.BANK_TRANSFER,
        )
        self.ticket = SupportTicket.objects.create(
            client=self.client_record,
            service=self.service,
            subject="Ajuda no alojamento",
            description="Preciso de ajuda.",
            channel=SupportTicket.Channel.PORTAL,
        )
        self.other_ticket = SupportTicket.objects.create(
            client=self.other_client,
            service=self.other_service,
            subject="Ticket privado",
            description="Não deve ser visível.",
        )
        self.document = Document.objects.create(
            title="Contrato do cliente",
            client=self.client_record,
            visible_to_client=True,
        )
        self.document.file.save("contrato.txt", ContentFile(b"Contrato ficticio"), save=True)
        self.other_document = Document.objects.create(
            title="Documento de outro cliente",
            client=self.other_client,
            visible_to_client=True,
        )
        self.other_document.file.save("outro.txt", ContentFile(b"Privado"), save=True)

    def login(self):
        self.client.force_login(self.user)

    def test_anonymous_user_is_redirected_to_portal_login(self):
        response = self.client.get(reverse("client_portal:dashboard"))
        self.assertRedirects(
            response,
            f'{reverse("client_portal:login")}?next={reverse("client_portal:dashboard")}',
        )

    def test_dashboard_and_main_sections_load(self):
        self.login()
        for name in ("dashboard", "services", "renewals", "payments", "documents", "tickets", "profile"):
            with self.subTest(name=name):
                response = self.client.get(reverse(f"client_portal:{name}"))
                self.assertEqual(response.status_code, 200)
        self.assertContains(self.client.get(reverse("client_portal:dashboard")), "Kreate4Web")

    def test_client_cannot_access_another_clients_objects(self):
        self.login()
        self.assertEqual(
            self.client.get(reverse("client_portal:service_detail", args=(self.other_service.pk,))).status_code,
            404,
        )
        self.assertEqual(
            self.client.get(reverse("client_portal:ticket_detail", args=(self.other_ticket.pk,))).status_code,
            404,
        )
        self.assertEqual(
            self.client.get(reverse("client_portal:document_download", args=(self.other_document.pk,))).status_code,
            404,
        )

    def test_document_download_is_protected_and_available_to_owner(self):
        self.login()
        response = self.client.get(reverse("client_portal:document_download", args=(self.document.pk,)))
        self.assertEqual(response.status_code, 200)
        self.assertIn("attachment", response["Content-Disposition"])

    def test_client_can_create_reply_resolve_and_reopen_ticket(self):
        self.login()
        response = self.client.post(
            reverse("client_portal:ticket_create"),
            {
                "service": self.service.pk,
                "subject": "Novo pedido",
                "category": SupportTicket.Category.HOSTING,
                "priority": SupportTicket.Priority.HIGH,
                "description": "Mensagem inicial do cliente.",
            },
        )
        self.assertEqual(response.status_code, 302)
        created = SupportTicket.objects.exclude(pk__in=[self.ticket.pk, self.other_ticket.pk]).get()
        self.assertEqual(created.client, self.client_record)
        self.assertEqual(created.channel, SupportTicket.Channel.PORTAL)
        self.assertEqual(created.messages.filter(author_type=SupportMessage.AuthorType.CLIENT).count(), 1)
        created.refresh_from_db()
        self.assertEqual(created.status, SupportTicket.Status.WAITING_INTERNAL)

        response = self.client.post(
            reverse("client_portal:ticket_detail", args=(created.pk,)),
            {"message": "Mais informação sobre o pedido."},
        )
        self.assertEqual(response.status_code, 302)
        self.assertEqual(created.messages.filter(author_type=SupportMessage.AuthorType.CLIENT).count(), 2)

        response = self.client.post(reverse("client_portal:ticket_resolve", args=(created.pk,)))
        self.assertEqual(response.status_code, 302)
        created.refresh_from_db()
        self.assertEqual(created.status, SupportTicket.Status.RESOLVED)
        self.assertTrue(created.resolved_by_client)
        self.assertIsNotNone(created.closed_at)

        response = self.client.post(reverse("client_portal:ticket_reopen", args=(created.pk,)))
        self.assertEqual(response.status_code, 302)
        created.refresh_from_db()
        self.assertEqual(created.status, SupportTicket.Status.REOPENED)
        self.assertIsNone(created.closed_at)

    def test_staff_public_reply_prepares_client_notification(self):
        PortalConfiguration.objects.create(
            company_name="Kreate4Web",
            company_email="admin@kreate4web.com",
            internal_notification_email="admin@kreate4web.com",
            auto_send_notifications=False,
        )
        with self.captureOnCommitCallbacks(execute=True):
            SupportMessage.objects.create(
                ticket=self.ticket,
                author_type=SupportMessage.AuthorType.STAFF,
                staff_user=self.staff,
                author_name="Admin Kreate4Web",
                message="Resposta pública da equipa.",
            )
        notification = NotificationLog.objects.get(client=self.client_record, recipient="cliente@example.com")
        self.assertEqual(notification.status, NotificationLog.Status.DRAFT)
        self.assertIn(self.ticket.reference, notification.subject)
        self.ticket.refresh_from_db()
        self.assertEqual(self.ticket.status, SupportTicket.Status.WAITING_CLIENT)

    def test_owner_can_update_contact_details(self):
        self.login()
        response = self.client.post(
            reverse("client_portal:profile"),
            {
                "action": "profile",
                "profile-email": "novo@example.com",
                "profile-alternative_email": "alternativo@example.com",
                "profile-phone": "912 345 678",
                "profile-address": "Rua de Teste, 1",
            },
        )
        self.assertRedirects(response, reverse("client_portal:profile"))
        self.client_record.refresh_from_db()
        self.assertEqual(self.client_record.email, "novo@example.com")
        self.assertEqual(self.client_record.phone, "912 345 678")

    def test_portal_user_can_change_password(self):
        self.login()
        response = self.client.post(
            reverse("client_portal:profile"),
            {
                "action": "password",
                "password-old_password": "Portal-Teste-2026!",
                "password-new_password1": "Nova-Password-Portal-2026!",
                "password-new_password2": "Nova-Password-Portal-2026!",
            },
        )
        self.assertRedirects(response, reverse("client_portal:profile"))
        self.user.refresh_from_db()
        self.assertTrue(self.user.check_password("Nova-Password-Portal-2026!"))

    def test_staff_login_is_redirected_to_admin(self):
        self.client.force_login(self.staff)
        response = self.client.get(reverse("client_portal:login"))
        self.assertRedirects(response, reverse("admin:index"))
