from datetime import timedelta
from decimal import Decimal

from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.files.base import ContentFile
from django.core.management import call_command
from django.core.management.base import BaseCommand
from django.db import transaction
from django.utils import timezone

from billing.models import Payment, Renewal
from billing.workflows import create_renewal_for_service, mark_payment_as_paid
from clients.models import Client, ClientContact, ClientNote, ClientPortalAccess
from core.choices import BillingCycle, ClientStatus, PaymentMethod, ServiceStatus, ServiceType
from core.demo_credentials import DEMO_PORTAL_ACCOUNTS
from core.models import Document, PortalConfiguration, Provider
from domains.models import Domain
from hosting.models import HostingAccount
from imports.models import ImportBatch, ImportedObject, ImportRow
from notifications.models import EmailSuppression, NotificationAttempt, NotificationLog, NotificationTemplate
from notifications.workflows import create_notification_from_template
from monitoring.models import MonitorCheck, MonitorIncident, WebsiteMonitor
from operations.models import InternalTask
from services.models import Service
from support.models import SupportMessage, SupportTicket
from technical.models import (
    BackupRecord, DatabaseAsset, Server, SSLCertificate, TechnicalAsset,
    WordPressPlugin, WordPressSite, WordPressTheme,
)
from template_library.models import FormTemplate, ReusableTemplate, TemplateUsage
from websites.models import WebsiteProject


class Command(BaseCommand):
    help = "Cria um conjunto coerente de dados totalmente fictícios para testar o backoffice."

    def add_arguments(self, parser):
        parser.add_argument(
            "--reset",
            action="store_true",
            help="Apaga todos os dados de negócio existentes antes de criar a demonstração. Usar apenas em desenvolvimento.",
        )

    @transaction.atomic
    def handle(self, *args, **options):
        if options["reset"]:
            self._reset_business_data()

        call_command("seed_initial_data", verbosity=0)
        today = timezone.localdate()
        now = timezone.now()

        config = PortalConfiguration.objects.first()
        if config:
            config.company_name = "Kreate4Web (DEMO)"
            config.company_email = "portal@kreate4web.demo.invalid"
            config.company_phone = "+351 910 000 000"
            config.portal_base_url = "http://127.0.0.1:8000"
            config.company_nif = "500000000"
            config.mbway_phone = "+351 910 000 000"
            config.iban = "PT50 0000 0000 0000 0000 0000 0"
            config.internal_notification_email = "alertas@demo.invalid"
            config.notes = "Configuração Kreate4Web totalmente fictícia, criada para testes locais."
            config.save()

        registrar = self._provider("Registo Fácil (DEMO)", Provider.ProviderType.REGISTRAR, "https://example.invalid")
        hosting_provider = self._provider("Cloud Norte (DEMO)", Provider.ProviderType.HOSTING, "https://example.invalid")
        server_provider = self._provider("Data Atlântico (DEMO)", Provider.ProviderType.SERVER, "https://example.invalid")
        payment_provider = self._provider("Pagamentos Teste (DEMO)", Provider.ProviderType.PAYMENT, "https://example.invalid")

        server = Server.objects.create(
            name="Servidor Lisboa 01 (DEMO)",
            provider=server_provider,
            hostname="lis01.demo.invalid",
            ip_address="192.0.2.10",
            location="Lisboa",
            control_panel="cPanel",
            panel_url="https://panel.demo.invalid",
            account_reference="Cofre DEMO / lis01",
            status=Server.Status.ACTIVE,
            notes="Endereço IP reservado para documentação; não representa um servidor real.",
        )
        backup_server = Server.objects.create(
            name="Armazenamento Porto (DEMO)",
            provider=server_provider,
            hostname="backup.demo.invalid",
            ip_address="198.51.100.20",
            location="Porto",
            control_panel="S3 compatível",
            account_reference="Cofre DEMO / backup",
            status=Server.Status.ACTIVE,
        )

        clients = {
            "acacia": self._client("Acácia Studio", "acacia@demo.invalid", "+351 910 111 111", PaymentMethod.MBWAY),
            "horizonte": self._client("Horizonte Verde, Lda.", "financeiro@horizonte.demo.invalid", "+351 910 222 222", PaymentMethod.BANK_TRANSFER),
            "oficina": self._client("Oficina do Norte", "geral@oficina.demo.invalid", "+351 910 333 333", PaymentMethod.BANK_TRANSFER, ClientStatus.DEBT),
            "clinica": self._client("Clínica Sorriso", "administracao@clinica.demo.invalid", "+351 910 444 444", PaymentMethod.MBWAY),
            "associacao": self._client("Associação Raiz Digital", "direcao@raiz.demo.invalid", "+351 910 555 555", PaymentMethod.BANK_TRANSFER, ClientStatus.SPECIAL),
            "joao": self._client("João Martins Fotografia", "joao@fotografia.demo.invalid", "+351 910 666 666", PaymentMethod.MBWAY, client_type=Client.ClientType.PERSON),
            "aurora": self._client("Loja Aurora", "loja@aurora.demo.invalid", "+351 910 777 777", PaymentMethod.BANK_TRANSFER),
            "sem_email": self._client("Cliente a confirmar", "", "+351 910 888 888", PaymentMethod.MBWAY, ClientStatus.PENDING),
        }

        # Contas fictícias para testar vários perfis e permissões em /portal/.
        self._portal_accounts(clients)

        ClientContact.objects.create(
            client=clients["horizonte"], name="Marta Campos", role="Responsável financeira", email="marta@horizonte.demo.invalid",
            phone="+351 910 222 223", is_primary=True, receives_billing=True, receives_technical=False,
        )
        ClientContact.objects.create(
            client=clients["clinica"], name="Ricardo Lima", role="Gestor", email="ricardo@clinica.demo.invalid",
            phone="+351 910 444 445", is_primary=True, receives_billing=True, receives_technical=True,
        )
        ClientNote.objects.create(
            client=clients["oficina"], title="Confirmar dados de faturação", body="Nota fictícia: pedir atualização do NIF e da morada.", is_important=True,
        )

        service_data = {}
        service_data["acacia_domain"], domains_acacia = self._domain_service(
            clients["acacia"], "Domínio acaciastudio.pt", "acaciastudio.pt", registrar, today + timedelta(days=20), Decimal("14.90"), Decimal("30.00")
        )
        service_data["acacia_hosting"] = self._hosting_service(
            clients["acacia"], "Alojamento Acácia Studio", hosting_provider, domains_acacia, server, today + timedelta(days=20), Decimal("58.00"), Decimal("120.00"), "Business 10 GB"
        )

        service_data["horizonte_domain"], domain_horizonte = self._domain_service(
            clients["horizonte"], "Domínio horizonteverde.pt", "horizonteverde.pt", registrar, today + timedelta(days=7), Decimal("16.50"), Decimal("35.00")
        )
        service_data["horizonte_hosting"] = self._hosting_service(
            clients["horizonte"], "Alojamento Horizonte Verde", hosting_provider, domain_horizonte, server, today + timedelta(days=7), Decimal("75.00"), Decimal("150.00"), "Pro 20 GB"
        )

        service_data["oficina_domain"], domain_oficina = self._domain_service(
            clients["oficina"], "Domínio oficinadonorte.pt", "oficinadonorte.pt", registrar, today - timedelta(days=5), Decimal("16.50"), Decimal("35.00"), service_status=ServiceStatus.OVERDUE, domain_status=Domain.DomainStatus.EXPIRED
        )
        service_data["oficina_hosting"] = self._hosting_service(
            clients["oficina"], "Alojamento Oficina do Norte", hosting_provider, domain_oficina, server, today - timedelta(days=5), Decimal("65.00"), Decimal("130.00"), "Business 15 GB", service_status=ServiceStatus.OVERDUE
        )

        service_data["clinica_domain"], domain_clinica = self._domain_service(
            clients["clinica"], "Domínio clinicasorriso.pt", "clinicasorriso.pt", registrar, today + timedelta(days=45), Decimal("16.50"), Decimal("35.00")
        )
        service_data["clinica_maintenance"] = self._service(
            clients["clinica"], ServiceType.MAINTENANCE, "Manutenção WordPress — Clínica Sorriso", hosting_provider,
            today + timedelta(days=10), Decimal("15.00"), Decimal("45.00"), BillingCycle.MONTHLY,
            description="Atualizações, segurança e pequenas alterações mensais.",
        )

        service_data["raiz_domain"], domain_raiz = self._domain_service(
            clients["associacao"], "Domínio raizdigital.org", "raizdigital.org", None, today + timedelta(days=75), Decimal("18.00"), Decimal("40.00")
        )
        service_data["raiz_hosting"] = self._hosting_service(
            clients["associacao"], "Alojamento Associação Raiz Digital", hosting_provider, domain_raiz, server, today + timedelta(days=75), Decimal("40.00"), Decimal("90.00"), "ONG 8 GB"
        )

        service_data["joao_website"] = self._service(
            clients["joao"], ServiceType.WEBSITE, "Website João Martins Fotografia", None, None,
            Decimal("0.00"), Decimal("950.00"), BillingCycle.NONE, is_recurring=False,
            description="Projeto único de website e portefólio.",
        )

        service_data["aurora_domain"], domain_aurora = self._domain_service(
            clients["aurora"], "Domínio lojaaurora.pt — oferta", "lojaaurora.pt", registrar, today + timedelta(days=120),
            Decimal("16.50"), Decimal("0.00"), complimentary=True, domain_status=Domain.DomainStatus.COMPLIMENTARY,
        )
        service_data["aurora_email"] = self._service(
            clients["aurora"], ServiceType.EMAIL, "5 contas de email Loja Aurora", hosting_provider, today + timedelta(days=15),
            Decimal("20.00"), Decimal("60.00"), BillingCycle.ANNUAL,
        )
        service_data["aurora_ssl"] = self._service(
            clients["aurora"], ServiceType.SSL, "Certificado SSL Loja Aurora", hosting_provider, today + timedelta(days=30),
            Decimal("0.00"), Decimal("25.00"), BillingCycle.ANNUAL,
        )

        service_data["incomplete_domain"], incomplete_domain = self._domain_service(
            clients["sem_email"], "Domínio por confirmar", "projetoteste.invalid", None, None,
            Decimal("0.00"), Decimal("0.00"), service_status=ServiceStatus.PENDING, domain_status=Domain.DomainStatus.PENDING,
        )

        # V7: cobertura completa de serviços por cliente e de todos os tipos de serviço.
        service_data["acacia_maintenance"] = self._service(
            clients["acacia"], ServiceType.MAINTENANCE, "Manutenção WordPress — Acácia Studio", hosting_provider,
            today + timedelta(days=20), Decimal("18.00"), Decimal("55.00"), BillingCycle.MONTHLY,
            description="Atualizações, backups, segurança e pequenas alterações.",
        )
        service_data["acacia_email"] = self._service(
            clients["acacia"], ServiceType.EMAIL, "10 contas de email — Acácia Studio", hosting_provider,
            today + timedelta(days=20), Decimal("24.00"), Decimal("72.00"), BillingCycle.ANNUAL,
        )
        service_data["horizonte_backup"] = self._service(
            clients["horizonte"], ServiceType.BACKUP, "Backup externo — Horizonte Verde", server_provider,
            today + timedelta(days=60), Decimal("30.00"), Decimal("90.00"), BillingCycle.ANNUAL,
        )
        service_data["horizonte_maintenance"] = self._service(
            clients["horizonte"], ServiceType.MAINTENANCE, "Manutenção trimestral — Horizonte Verde", hosting_provider,
            today + timedelta(days=14), Decimal("45.00"), Decimal("120.00"), BillingCycle.QUARTERLY,
        )
        service_data["oficina_email"] = self._service(
            clients["oficina"], ServiceType.EMAIL, "Emails profissionais — Oficina do Norte", hosting_provider,
            today - timedelta(days=5), Decimal("20.00"), Decimal("65.00"), BillingCycle.ANNUAL,
            service_status=ServiceStatus.OVERDUE,
        )
        service_data["oficina_ssl"] = self._service(
            clients["oficina"], ServiceType.SSL, "Certificado SSL — Oficina do Norte", hosting_provider,
            today - timedelta(days=2), Decimal("0.00"), Decimal("30.00"), BillingCycle.ANNUAL,
            service_status=ServiceStatus.ATTENTION,
        )
        service_data["clinica_hosting"] = self._hosting_service(
            clients["clinica"], "Alojamento Clínica Sorriso", hosting_provider, domain_clinica, server,
            today + timedelta(days=45), Decimal("85.00"), Decimal("165.00"), "Clinical Pro 25 GB"
        )
        service_data["clinica_email"] = self._service(
            clients["clinica"], ServiceType.EMAIL, "20 contas de email — Clínica Sorriso", hosting_provider,
            today + timedelta(days=45), Decimal("45.00"), Decimal("120.00"), BillingCycle.ANNUAL,
        )
        service_data["raiz_ssl"] = self._service(
            clients["associacao"], ServiceType.SSL, "Certificado SSL — Raiz Digital", hosting_provider,
            today + timedelta(days=75), Decimal("0.00"), Decimal("25.00"), BillingCycle.ANNUAL,
        )
        service_data["raiz_backup"] = self._service(
            clients["associacao"], ServiceType.BACKUP, "Backup diário — Raiz Digital", server_provider,
            today + timedelta(days=30), Decimal("25.00"), Decimal("70.00"), BillingCycle.ANNUAL,
        )
        service_data["raiz_server"] = self._service(
            clients["associacao"], ServiceType.SERVER, "Servidor virtual gerido — Raiz Digital", server_provider,
            today + timedelta(days=90), Decimal("240.00"), Decimal("480.00"), BillingCycle.ANNUAL,
            description="Servidor virtual fictício com manutenção e monitorização.",
        )
        service_data["joao_domain"], domain_joao = self._domain_service(
            clients["joao"], "Domínio joaomartinsfoto.pt", "joaomartinsfoto.pt", registrar,
            today + timedelta(days=200), Decimal("16.50"), Decimal("35.00")
        )
        service_data["joao_hosting"] = self._hosting_service(
            clients["joao"], "Alojamento portefólio João Martins", hosting_provider, domain_joao, server,
            today + timedelta(days=200), Decimal("48.00"), Decimal("100.00"), "Portfolio 8 GB"
        )
        service_data["joao_backup"] = self._service(
            clients["joao"], ServiceType.BACKUP, "Backup mensal do portefólio", server_provider,
            today + timedelta(days=200), Decimal("12.00"), Decimal("36.00"), BillingCycle.ANNUAL,
        )
        service_data["aurora_hosting"] = self._hosting_service(
            clients["aurora"], "Alojamento Loja Aurora", hosting_provider, domain_aurora, server,
            today + timedelta(days=120), Decimal("95.00"), Decimal("190.00"), "E-commerce 30 GB"
        )
        service_data["aurora_maintenance"] = self._service(
            clients["aurora"], ServiceType.MAINTENANCE, "Manutenção e-commerce — Loja Aurora", hosting_provider,
            today + timedelta(days=5), Decimal("30.00"), Decimal("85.00"), BillingCycle.MONTHLY,
        )
        service_data["aurora_other"] = self._service(
            clients["aurora"], ServiceType.OTHER, "Consultoria de conteúdos — Loja Aurora", payment_provider,
            None, Decimal("0.00"), Decimal("180.00"), BillingCycle.NONE, is_recurring=False,
            description="Serviço pontual fictício para validar o tipo Outro.",
        )
        service_data["incomplete_hosting"] = self._hosting_service(
            clients["sem_email"], "Alojamento por confirmar", hosting_provider, incomplete_domain, server,
            None, Decimal("0.00"), Decimal("0.00"), "Plano por definir", service_status=ServiceStatus.PENDING,
        )

        self._website_projects(clients, domain_clinica, domains_acacia, service_data, today)
        self._technical_assets(server, backup_server, service_data, today, now)
        self._wordpress_and_ssl_data(today, now)
        self._monitoring_data(today, now)
        renewals = self._renewals_and_payments(service_data, today, now)
        self._notifications(renewals, now)
        self._tasks(clients, service_data, renewals, today)
        self._support_and_documents(clients, service_data, renewals, today, now)
        self._complete_demo_coverage(clients, service_data, renewals, today, now)
        call_command("export_demo_credentials", verbosity=0)

        self.stdout.write(self.style.SUCCESS("Dados fictícios V9.4 criados com sucesso."))
        self.stdout.write("Abra /admin/ e /portal/ para testar todos os módulos, perfis, templates, serviços, pagamentos, suporte e infraestrutura.")
        self.stdout.write(self.style.WARNING("Todos os nomes, contactos, domínios e valores deste conjunto são fictícios."))

    def _reset_business_data(self):
        TemplateUsage.objects.all().delete()
        ImportedObject.objects.all().delete()
        ImportRow.objects.all().delete()
        ImportBatch.objects.all().delete()
        Document.objects.all().delete()
        ClientPortalAccess.objects.all().delete()
        get_user_model().objects.filter(username__in=[item["username"] for item in DEMO_PORTAL_ACCOUNTS]).delete()
        from notifications.models import EmailSuppression, NotificationAttempt
        NotificationAttempt.objects.all().delete()
        EmailSuppression.objects.all().delete()
        SupportMessage.objects.all().delete()
        SupportTicket.objects.all().delete()
        NotificationLog.objects.all().delete()
        Payment.objects.all().delete()
        InternalTask.objects.all().delete()
        Renewal.objects.all().delete()
        MonitorIncident.objects.all().delete()
        MonitorCheck.objects.all().delete()
        WebsiteMonitor.objects.all().delete()
        WordPressPlugin.objects.all().delete()
        WordPressTheme.objects.all().delete()
        WordPressSite.objects.all().delete()
        SSLCertificate.objects.all().delete()
        BackupRecord.objects.all().delete()
        DatabaseAsset.objects.all().delete()
        TechnicalAsset.objects.all().delete()
        HostingAccount.objects.all().delete()
        WebsiteProject.objects.all().delete()
        Domain.objects.all().delete()
        Service.objects.all().delete()
        ClientNote.objects.all().delete()
        ClientContact.objects.all().delete()
        Client.objects.all().delete()
        Server.objects.all().delete()
        Provider.objects.all().delete()

    def _portal_accounts(self, clients):
        User = get_user_model()
        for account in DEMO_PORTAL_ACCOUNTS:
            client = clients[account["client_key"]]
            if not client.portal_access_enabled:
                client.portal_access_enabled = True
                client.save(update_fields=["portal_access_enabled", "updated_at"])
            user, _ = User.objects.get_or_create(username=account["username"])
            user.email = account["email"]
            user.first_name = account["first_name"]
            user.last_name = account["last_name"]
            user.is_staff = False
            user.is_superuser = False
            user.is_active = True
            user.set_password(account["password"])
            user.save()
            ClientPortalAccess.objects.update_or_create(
                user=user,
                defaults={
                    "client": client,
                    "role": account["role"],
                    "is_active": True,
                    "can_view_financial": account["can_view_financial"],
                    "can_view_documents": account["can_view_documents"],
                    "can_manage_tickets": account["can_manage_tickets"],
                    "accepted_at": timezone.now(),
                },
            )

    def _provider(self, name, provider_type, website):
        return Provider.objects.create(
            name=name,
            provider_type=provider_type,
            website=website,
            support_email="suporte@demo.invalid",
            support_phone="+351 210 000 000",
            account_reference=f"Cofre DEMO / {name}",
            notes="Fornecedor fictício criado para testes.",
        )

    def _client(self, name, email, phone, payment_method, status=ClientStatus.ACTIVE, client_type=Client.ClientType.COMPANY):
        slug = name.lower().replace(" ", "").replace(".", "")[:12]
        return Client.objects.create(
            client_type=client_type,
            name=name,
            legal_name=name if client_type == Client.ClientType.COMPANY else "",
            nif="500000000" if client_type == Client.ClientType.COMPANY else "",
            email=email,
            phone=phone,
            address="Morada fictícia, 0000-000 Portugal",
            status=status,
            preferred_payment_method=payment_method,
            portal_access_enabled=False,
            internal_notes=f"Registo DEMO ({slug}). Não corresponde a um cliente real.",
        )

    def _service(self, client, service_type, name, provider, renewal_date, cost, sale, cycle, service_status=ServiceStatus.ACTIVE, is_recurring=True, complimentary=False, description=""):
        service = Service(
            client=client,
            service_type=service_type,
            name=name,
            provider=provider,
            description=description,
            status=service_status,
            start_date=timezone.localdate() - timedelta(days=365),
            next_renewal_date=renewal_date,
            billing_cycle=cycle,
            notice_days=30,
            cost_price=cost,
            sale_price=sale,
            tax_rate=Decimal("23.00"),
            auto_renew=False,
            is_recurring=is_recurring,
            is_complimentary=complimentary,
            internal_notes="Dados fictícios para validação do fluxo.",
        )
        service.full_clean()
        service.save()
        return service

    def _domain_service(self, client, service_name, domain_name, registrar, renewal_date, cost, sale, service_status=ServiceStatus.ACTIVE, domain_status=Domain.DomainStatus.ACTIVE, complimentary=False):
        service = self._service(
            client, ServiceType.DOMAIN, service_name, registrar, renewal_date, cost, sale, BillingCycle.ANNUAL,
            service_status=service_status, complimentary=complimentary,
        )
        domain = Domain.objects.create(
            service=service,
            domain_name=domain_name,
            registrar=registrar,
            registered_on=timezone.localdate() - timedelta(days=900),
            status=domain_status,
            auto_renew_at_registrar=False,
            dns_management=Domain.DNSManagement.INTERNAL,
            nameservers="ns1.demo.invalid\nns2.demo.invalid",
            transfer_lock=True,
            auth_code_reference=f"Cofre DEMO / {domain_name}",
            notes="Domínio fictício.",
        )
        return service, domain

    def _hosting_service(self, client, service_name, provider, primary_domain, server, renewal_date, cost, sale, plan_name, service_status=ServiceStatus.ACTIVE):
        service = self._service(
            client, ServiceType.HOSTING, service_name, provider, renewal_date, cost, sale, BillingCycle.ANNUAL,
            service_status=service_status,
        )
        HostingAccount.objects.create(
            service=service,
            primary_domain=primary_domain,
            server=server,
            plan_name=plan_name,
            panel_type=HostingAccount.PanelType.CPANEL,
            account_reference=f"Cofre DEMO / {primary_domain.domain_name}",
            document_root=f"/home/demo/public_html/{primary_domain.domain_name}",
            disk_quota_mb=10240,
            disk_used_mb=2450,
            monthly_traffic_mb=1200,
            email_account_limit=20,
            php_version="8.3",
            status=HostingAccount.Status.ACTIVE if service_status == ServiceStatus.ACTIVE else HostingAccount.Status.SUSPENDED,
            notes="Conta fictícia para teste.",
        )
        return service

    def _website_projects(self, clients, domain_clinica, domain_acacia, services, today):
        projects = []
        p1 = WebsiteProject.objects.create(
            client=clients["acacia"], title="Website institucional Acácia Studio", primary_domain=domain_acacia,
            status=WebsiteProject.Status.MAINTENANCE, start_date=today - timedelta(days=500), delivery_date=today - timedelta(days=430),
            contracted_value=Decimal("1200.00"), historical_received_value=Decimal("1200.00"), theme_name="Tema personalizado",
            technology="WordPress", repository_reference="Repo DEMO / acacia", notes="Projeto fictício entregue.",
        )
        p1.recurring_services.add(services["acacia_domain"], services["acacia_hosting"], services["acacia_maintenance"], services["acacia_email"])
        projects.append(p1)

        p2 = WebsiteProject.objects.create(
            client=clients["clinica"], title="Novo website Clínica Sorriso", primary_domain=domain_clinica,
            status=WebsiteProject.Status.DEVELOPMENT, start_date=today - timedelta(days=20), contracted_value=Decimal("1800.00"),
            historical_received_value=Decimal("900.00"), partner="Designer DEMO", theme_name="Design próprio", technology="Django",
            repository_reference="Repo DEMO / clinica", notes="Projeto fictício em desenvolvimento.",
        )
        p2.recurring_services.add(services["clinica_domain"], services["clinica_hosting"], services["clinica_maintenance"], services["clinica_email"])
        projects.append(p2)

        p3 = WebsiteProject.objects.create(
            client=clients["joao"], title="Portefólio João Martins", primary_domain=services["joao_domain"].domain_details,
            status=WebsiteProject.Status.QUOTE, start_date=today, contracted_value=Decimal("950.00"), historical_received_value=Decimal("0.00"),
            theme_name="A definir", technology="A definir", notes="Orçamento fictício.",
        )
        p3.recurring_services.add(services["joao_website"], services["joao_domain"], services["joao_hosting"], services["joao_backup"])
        projects.append(p3)

        project_specs = [
            ("horizonte", "Portal de sustentabilidade Horizonte Verde", "horizonte_domain", WebsiteProject.Status.REVIEW, "Django", Decimal("2400.00"), Decimal("1800.00")),
            ("oficina", "Website Oficina do Norte", "oficina_domain", WebsiteProject.Status.MAINTENANCE, "WordPress", Decimal("1100.00"), Decimal("1100.00")),
            ("associacao", "Portal Associação Raiz Digital", "raiz_domain", WebsiteProject.Status.DELIVERED, "Drupal", Decimal("3200.00"), Decimal("2800.00")),
            ("aurora", "Loja online Aurora", "aurora_domain", WebsiteProject.Status.PARTIAL, "WooCommerce", Decimal("2800.00"), Decimal("1400.00")),
            ("sem_email", "Projeto a confirmar", "incomplete_domain", WebsiteProject.Status.WAITING, "A definir", Decimal("0.00"), Decimal("0.00")),
        ]
        for client_key, title, domain_key, status, technology, contracted, received in project_specs:
            project = WebsiteProject.objects.create(
                client=clients[client_key],
                title=title,
                primary_domain=services[domain_key].domain_details,
                status=status,
                start_date=today - timedelta(days=120),
                delivery_date=today - timedelta(days=30) if status in {WebsiteProject.Status.DELIVERED, WebsiteProject.Status.MAINTENANCE} else None,
                contracted_value=contracted,
                historical_received_value=received,
                theme_name="Template DEMO / design personalizado",
                technology=technology,
                repository_reference=f"Repo DEMO / {client_key}",
                notes="Projeto inteiramente fictício para testar estados, filtros e relações.",
            )
            related = [service for key, service in services.items() if key.startswith(client_key if client_key != "associacao" else "raiz_")]
            if related:
                project.recurring_services.add(*related)
            projects.append(project)
        return projects

    def _technical_assets(self, server, backup_server, services, today, now):
        website_specs = [
            ("acacia_hosting", "acaciastudio.pt", "demo_acacia", "WordPress", TechnicalAsset.Status.ACTIVE, True, BackupRecord.Status.VERIFIED),
            ("horizonte_hosting", "horizonteverde.pt", "demo_horizonte", "WordPress", TechnicalAsset.Status.MAINTENANCE, True, BackupRecord.Status.CREATED),
            ("oficina_hosting", "oficinadonorte.pt", "demo_oficina", "WordPress", TechnicalAsset.Status.CRITICAL, False, BackupRecord.Status.FAILED),
            ("clinica_hosting", "clinicasorriso.pt", "demo_clinica", "Django", TechnicalAsset.Status.ACTIVE, True, BackupRecord.Status.VERIFIED),
            ("raiz_hosting", "raizdigital.org", "demo_raiz", "Drupal", TechnicalAsset.Status.ATTENTION, True, BackupRecord.Status.SCHEDULED),
            ("joao_hosting", "joaomartinsfoto.pt", "demo_joao", "WordPress", TechnicalAsset.Status.ACTIVE, True, BackupRecord.Status.VERIFIED),
            ("aurora_hosting", "lojaaurora.pt", "demo_aurora", "WooCommerce", TechnicalAsset.Status.ACTIVE, True, BackupRecord.Status.RUNNING),
            ("incomplete_hosting", "projetoteste.invalid", "demo_confirmar", "Por confirmar", TechnicalAsset.Status.UNKNOWN, False, BackupRecord.Status.EXPIRED),
        ]
        for key, domain, db_name, cms, asset_status, ssl_active, backup_status in website_specs:
            asset = TechnicalAsset.objects.create(
                service=services[key], server=server, asset_type=TechnicalAsset.AssetType.WEBSITE,
                label=f"Website {domain}", domain_name=domain, document_root=f"/home/demo/public_html/{domain}",
                subdomain="", php_version="8.3" if asset_status != TechnicalAsset.Status.UNKNOWN else "",
                cms=cms, cms_version="6.x" if cms in {"Drupal", "WordPress"} else "",
                ssl_active=ssl_active, last_checked_at=now - timedelta(days=2),
                status=asset_status, notes="Ativo técnico fictício para cobertura completa do portal.",
            )
            DatabaseAsset.objects.create(
                technical_asset=asset, name=db_name,
                engine=DatabaseAsset.Engine.POSTGRES if cms == "Django" else DatabaseAsset.Engine.MYSQL,
                size_mb=Decimal("128.50"), username_reference=f"Cofre DEMO / {db_name}", notes="Base de dados fictícia.",
            )
            BackupRecord.objects.create(
                technical_asset=asset, backup_type=BackupRecord.BackupType.FULL,
                backup_date=now - timedelta(days=1), location_reference=f"{backup_server.name} / {domain}",
                size_mb=Decimal("540.00"),
                verified_at=now - timedelta(hours=20) if backup_status == BackupRecord.Status.VERIFIED else None,
                status=backup_status, notes="Backup fictício com estado variado para testar cores e filtros.",
            )

        # Ativos não-web para testar todos os tipos da infraestrutura.
        for service_key, asset_type, label, status in [
            ("acacia_email", TechnicalAsset.AssetType.MAILBOX, "Caixas de email Acácia", TechnicalAsset.Status.ACTIVE),
            ("oficina_email", TechnicalAsset.AssetType.MAILBOX, "Caixas de email Oficina", TechnicalAsset.Status.ATTENTION),
            ("aurora_ssl", TechnicalAsset.AssetType.SSL, "SSL Loja Aurora", TechnicalAsset.Status.ACTIVE),
            ("raiz_backup", TechnicalAsset.AssetType.BACKUP, "Política de backup Raiz", TechnicalAsset.Status.MAINTENANCE),
            ("raiz_server", TechnicalAsset.AssetType.PANEL_ACCOUNT, "Conta de servidor Raiz", TechnicalAsset.Status.ACTIVE),
            ("aurora_other", TechnicalAsset.AssetType.OTHER, "Ativo documental Aurora", TechnicalAsset.Status.INACTIVE),
        ]:
            TechnicalAsset.objects.create(
                service=services[service_key], server=server if asset_type != TechnicalAsset.AssetType.OTHER else None,
                asset_type=asset_type, label=label, last_checked_at=now - timedelta(days=3),
                ssl_active=asset_type == TechnicalAsset.AssetType.SSL, status=status,
                notes="Ativo adicional fictício para testar a cobertura de tipos.",
            )

    def _wordpress_and_ssl_data(self, today, now):
        """Cria dados V9.1 para WordPress, plugins, temas e certificados SSL."""
        wp_specs = {
            "acaciastudio.pt": {"health": WordPressSite.HealthStatus.HEALTHY, "installed": "6.6.2", "available": "6.7.1"},
            "horizonteverde.pt": {"health": WordPressSite.HealthStatus.ATTENTION, "installed": "6.5.5", "available": "6.7.1"},
            "oficinadonorte.pt": {"health": WordPressSite.HealthStatus.CRITICAL, "installed": "6.4.4", "available": "6.7.1", "debug": True},
            "joaomartinsfoto.pt": {"health": WordPressSite.HealthStatus.HEALTHY, "installed": "6.7.1", "available": "6.7.1"},
            "lojaaurora.pt": {"health": WordPressSite.HealthStatus.ATTENTION, "installed": "6.6.1", "available": "6.7.1"},
        }
        for domain, spec in wp_specs.items():
            asset = TechnicalAsset.objects.filter(domain_name=domain).first()
            if not asset:
                continue
            site = WordPressSite.objects.create(
                technical_asset=asset,
                public_url=f"https://{domain}",
                admin_url=f"https://{domain}/wp-admin/",
                installed_version=spec["installed"],
                available_version=spec["available"],
                php_version=asset.php_version or "8.2",
                multisite=False,
                wp_cron_enabled=domain != "oficinadonorte.pt",
                automatic_core_updates=domain in {"acaciastudio.pt", "joaomartinsfoto.pt"},
                debug_mode=spec.get("debug", False),
                maintenance_mode=domain == "oficinadonorte.pt",
                credentials_reference=f"Cofre DEMO / WordPress / {domain}",
                health_status=spec["health"],
                last_updated_at=now - timedelta(days=3 if spec["health"] == WordPressSite.HealthStatus.HEALTHY else 45),
                last_scan_at=now - timedelta(hours=8),
                notes="Instalação WordPress fictícia criada para validar a V9.1.",
            )
            plugin_specs = [
                ("Wordfence Security", "wordfence", "7.11.7", "7.11.7", WordPressPlugin.SecurityStatus.SECURE, False, None),
                ("Advanced Custom Fields PRO", "advanced-custom-fields-pro", "6.2.7", "6.3.1", WordPressPlugin.SecurityStatus.ATTENTION, True, today + timedelta(days=25)),
                ("Classic Editor", "classic-editor", "1.6.3", "1.6.5", WordPressPlugin.SecurityStatus.SECURE, False, None),
            ]
            if domain == "oficinadonorte.pt":
                plugin_specs.append(("Plugin Legacy DEMO", "legacy-demo", "1.0", "2.4", WordPressPlugin.SecurityStatus.VULNERABLE, False, None))
            if domain == "lojaaurora.pt":
                plugin_specs.append(("WooCommerce", "woocommerce", "9.1.0", "9.4.2", WordPressPlugin.SecurityStatus.ATTENTION, False, None))
            for name, slug, installed, available, security, premium, expiry in plugin_specs:
                WordPressPlugin.objects.create(
                    wordpress_site=site, name=name, slug=slug, installed_version=installed,
                    available_version=available, activation_status=WordPressPlugin.ActivationStatus.ACTIVE,
                    automatic_updates=name in {"Wordfence Security", "Classic Editor"}, premium=premium,
                    provider="Fornecedor DEMO" if premium else "WordPress.org",
                    license_reference=f"Cofre DEMO / Licença / {slug}" if premium else "",
                    license_expires_on=expiry, security_status=security, last_checked_at=now - timedelta(hours=8),
                    notes="Plugin fictício; não representa software instalado num site real.",
                )
            WordPressTheme.objects.create(
                wordpress_site=site, name="K4W Child Theme", slug="k4w-child",
                installed_version="2.1.0", available_version="2.1.0", is_active=True,
                is_child_theme=True, parent_theme="GeneratePress", automatic_updates=False,
                premium=False, last_checked_at=now - timedelta(hours=8),
                notes="Tema filho fictício para validar a gestão de temas.",
            )
            WordPressTheme.objects.create(
                wordpress_site=site, name="GeneratePress Premium", slug="generatepress",
                installed_version="3.4.0", available_version="3.5.1", is_active=False,
                is_child_theme=False, automatic_updates=False, premium=True, provider="GeneratePress",
                license_reference="Cofre DEMO / GeneratePress", license_expires_on=today + timedelta(days=60),
                last_checked_at=now - timedelta(hours=8),
            )

        for asset in TechnicalAsset.objects.filter(asset_type=TechnicalAsset.AssetType.WEBSITE):
            if not asset.domain_name:
                continue
            if asset.ssl_active:
                days = 18 if asset.domain_name == "horizonteverde.pt" else 72
                status = SSLCertificate.Status.EXPIRING if days <= 30 else SSLCertificate.Status.VALID
                expires_at = now + timedelta(days=days)
                error = ""
            else:
                status = SSLCertificate.Status.EXPIRED
                expires_at = now - timedelta(days=4)
                error = "Certificado fictício expirado para testar alertas."
            SSLCertificate.objects.create(
                technical_asset=asset, primary_domain=asset.domain_name,
                covered_domains=f"{asset.domain_name}\nwww.{asset.domain_name}",
                issuer="Let's Encrypt (DEMO)", certificate_type=SSLCertificate.CertificateType.LETS_ENCRYPT,
                issued_at=expires_at - timedelta(days=90), expires_at=expires_at,
                automatic_renewal=asset.ssl_active, validation_method=SSLCertificate.ValidationMethod.HTTP,
                status=status, last_checked_at=now - timedelta(hours=6), error_message=error,
                notes="Certificado inteiramente fictício para a demonstração V9.1.",
            )


    def _monitoring_data(self, today, now):
        """Cria monitores, verificações e incidentes fictícios para a V9.2."""
        specs = {
            "acaciastudio.pt": (WebsiteMonitor.Status.UP, 420, 200),
            "horizonteverde.pt": (WebsiteMonitor.Status.DEGRADED, 3180, 200),
            "oficinadonorte.pt": (WebsiteMonitor.Status.DOWN, 12000, 503),
            "clinicasorriso.pt": (WebsiteMonitor.Status.UP, 690, 200),
            "raizdigital.org": (WebsiteMonitor.Status.UP, 510, 200),
            "joaomartinsfoto.pt": (WebsiteMonitor.Status.UP, 880, 200),
            "lojaaurora.pt": (WebsiteMonitor.Status.MAINTENANCE, None, None),
            "projetoteste.invalid": (WebsiteMonitor.Status.PAUSED, None, None),
        }
        monitors = {}
        for domain, (status, response_ms, http_status) in specs.items():
            asset = TechnicalAsset.objects.filter(domain_name=domain, asset_type=TechnicalAsset.AssetType.WEBSITE).first()
            if not asset:
                continue
            active = status != WebsiteMonitor.Status.PAUSED
            maintenance_until = now + timedelta(hours=5) if status == WebsiteMonitor.Status.MAINTENANCE else None
            monitor = WebsiteMonitor.objects.create(
                technical_asset=asset,
                name=f"Website {domain}",
                url=f"https://{domain}",
                is_active=active,
                client_visible=True,
                interval_minutes=5,
                timeout_seconds=12,
                warning_response_ms=2500,
                alert_after_failures=2,
                recover_after_successes=1,
                notify_internal=True,
                notify_client=domain in {"oficinadonorte.pt", "horizonteverde.pt"},
                status=status,
                maintenance_until=maintenance_until,
                last_checked_at=now - timedelta(minutes=4) if response_ms else None,
                next_check_at=(maintenance_until if maintenance_until else now + timedelta(minutes=1)) if active else None,
                last_success_at=now - timedelta(minutes=4) if status in {WebsiteMonitor.Status.UP, WebsiteMonitor.Status.DEGRADED} else None,
                last_failure_at=now - timedelta(minutes=4) if status == WebsiteMonitor.Status.DOWN else None,
                last_response_ms=response_ms,
                last_http_status=http_status,
                last_error="HTTP 503 — Serviço temporariamente indisponível." if status == WebsiteMonitor.Status.DOWN else "",
                consecutive_failures=3 if status == WebsiteMonitor.Status.DOWN else 0,
                consecutive_successes=8 if status in {WebsiteMonitor.Status.UP, WebsiteMonitor.Status.DEGRADED} else 0,
            )
            monitors[domain] = monitor
            if status in {WebsiteMonitor.Status.PAUSED, WebsiteMonitor.Status.MAINTENANCE}:
                MonitorCheck.objects.create(
                    monitor=monitor, checked_at=now - timedelta(minutes=4),
                    result=MonitorCheck.Result.SKIPPED if status == WebsiteMonitor.Status.PAUSED else MonitorCheck.Result.MAINTENANCE,
                    error_message="Monitorização pausada." if status == WebsiteMonitor.Status.PAUSED else "Janela de manutenção programada.",
                )
                continue
            for idx in range(12, 0, -1):
                checked = now - timedelta(hours=idx * 2)
                is_down = status == WebsiteMonitor.Status.DOWN and idx <= 3
                is_degraded = status == WebsiteMonitor.Status.DEGRADED
                result = MonitorCheck.Result.DOWN if is_down else (MonitorCheck.Result.DEGRADED if is_degraded else MonitorCheck.Result.UP)
                MonitorCheck.objects.create(
                    monitor=monitor, checked_at=checked, result=result,
                    http_status=503 if is_down else 200,
                    response_time_ms=(11000 + idx * 40) if is_down else ((3000 + idx * 12) if is_degraded else max(220, (response_ms or 500) + idx * 5)),
                    final_url=f"https://{domain}/", redirect_count=1, resolved_ip="192.0.2.44",
                    response_size_bytes=52480, content_match=True if not is_down else None,
                    ssl_valid=True, ssl_issuer="Let's Encrypt (DEMO)",
                    ssl_expires_at=now + timedelta(days=55), ssl_days_remaining=55,
                    error_type=MonitorCheck.ErrorType.HTTP if is_down else "",
                    error_message="HTTP 503 — Serviço temporariamente indisponível." if is_down else "",
                    details={"demo": True},
                )

        down = monitors.get("oficinadonorte.pt")
        if down:
            first = down.checks.filter(result=MonitorCheck.Result.DOWN).order_by("checked_at").first()
            last = down.checks.filter(result=MonitorCheck.Result.DOWN).order_by("-checked_at").first()
            task = InternalTask.objects.create(
                title="Investigar indisponibilidade — Oficina do Norte",
                task_type=InternalTask.TaskType.TECHNICAL_CHECK, client=down.technical_asset.service.client,
                service=down.technical_asset.service, priority=InternalTask.Priority.URGENT,
                status=InternalTask.Status.IN_PROGRESS, due_date=today,
                description="Incidente fictício criado para testar o centro de monitorização.",
            )
            MonitorIncident.objects.create(
                monitor=down, title="Indisponibilidade detetada — Oficina do Norte",
                reason="HTTP 503 registado em três verificações consecutivas.",
                severity=MonitorIncident.Severity.CRITICAL, status=MonitorIncident.Status.OPEN,
                opened_at=now - timedelta(hours=5), first_check=first, last_check=last, failure_count=3,
                internal_task=task, client_visible=True,
            )

        degraded = monitors.get("horizonteverde.pt")
        if degraded:
            first = degraded.checks.order_by("checked_at").first()
            last = degraded.checks.order_by("-checked_at").first()
            MonitorIncident.objects.create(
                monitor=degraded, title="Lentidão temporária — Horizonte Verde",
                reason="Tempo de resposta acima de 2,5 segundos.", severity=MonitorIncident.Severity.WARNING,
                status=MonitorIncident.Status.RESOLVED, opened_at=now - timedelta(days=4, hours=2),
                resolved_at=now - timedelta(days=4), first_check=first, last_check=last, failure_count=2,
                client_visible=True, resolution_note="Desempenho normalizado após otimização de cache.",
            )

    def _renewals_and_payments(self, services, today, now):
        renewals = {}
        for key, service in services.items():
            if not service.is_recurring or not service.next_renewal_date:
                continue
            renewal, _ = create_renewal_for_service(service)
            renewals[key] = renewal

        renewals["acacia_domain"].status = Renewal.Status.WAITING_PAYMENT
        renewals["acacia_domain"].save()
        Payment.objects.create(
            renewal=renewals["acacia_domain"], amount=renewals["acacia_domain"].sale_amount,
            method=PaymentMethod.MBWAY, status=Payment.Status.WAITING_PROOF, reference="DEMO-MBW-001",
            notes="Pagamento fictício a aguardar comprovativo.",
        )

        renewals["horizonte_domain"].status = Renewal.Status.NOTIFIED
        renewals["horizonte_domain"].notice_30_sent_at = now - timedelta(days=20)
        renewals["horizonte_domain"].notice_15_sent_at = now - timedelta(days=5)
        renewals["horizonte_domain"].save()
        Payment.objects.create(
            renewal=renewals["horizonte_domain"], amount=renewals["horizonte_domain"].sale_amount,
            method=PaymentMethod.BANK_TRANSFER, status=Payment.Status.PENDING, reference="DEMO-TRF-002",
        )

        for key in ("oficina_domain", "oficina_hosting"):
            renewals[key].status = Renewal.Status.OVERDUE
            renewals[key].overdue_notice_sent_at = now - timedelta(days=1)
            renewals[key].save()
            Payment.objects.create(
                renewal=renewals[key], amount=renewals[key].sale_amount,
                method=PaymentMethod.BANK_TRANSFER, status=Payment.Status.PENDING, reference=f"DEMO-ATR-{key}",
            )

        payment = Payment.objects.create(
            renewal=renewals["clinica_domain"], amount=renewals["clinica_domain"].sale_amount,
            method=PaymentMethod.MBWAY, status=Payment.Status.PENDING, reference="DEMO-PAGO-003",
        )
        mark_payment_as_paid(payment, now - timedelta(days=2))

        partial_amount = Decimal("20.00")
        partial = Payment.objects.create(
            renewal=renewals["clinica_maintenance"], amount=partial_amount,
            method=PaymentMethod.BANK_TRANSFER, status=Payment.Status.PENDING, reference="DEMO-PARCIAL-004",
        )
        mark_payment_as_paid(partial, now - timedelta(days=1))

        renewals["aurora_domain"].status = Renewal.Status.PAID
        renewals["aurora_domain"].paid_at = now
        renewals["aurora_domain"].save()

        # Estados adicionais para testar todo o ciclo financeiro.
        renewals["acacia_hosting"].status = Renewal.Status.WAITING_PAYMENT
        renewals["acacia_hosting"].save(update_fields=["status", "updated_at"])
        Payment.objects.create(
            renewal=renewals["acacia_hosting"], amount=renewals["acacia_hosting"].sale_amount,
            method=PaymentMethod.BANK_TRANSFER, status=Payment.Status.VALIDATING,
            reference="DEMO-VALIDAR-005", notes="Comprovativo fictício em validação.",
        )

        paid_maintenance = Payment.objects.create(
            renewal=renewals["acacia_maintenance"], amount=renewals["acacia_maintenance"].sale_amount,
            method=PaymentMethod.CARD, status=Payment.Status.PENDING, reference="DEMO-CARD-006",
        )
        mark_payment_as_paid(paid_maintenance, now - timedelta(days=3))

        renewals["horizonte_hosting"].status = Renewal.Status.OVERDUE
        renewals["horizonte_hosting"].save(update_fields=["status", "updated_at"])
        Payment.objects.create(
            renewal=renewals["horizonte_hosting"], amount=renewals["horizonte_hosting"].sale_amount,
            method=PaymentMethod.BANK_TRANSFER, status=Payment.Status.OVERDUE, reference="DEMO-OVERDUE-007",
        )

        renewals["raiz_server"].status = Renewal.Status.PROCESSING
        renewals["raiz_server"].save(update_fields=["status", "updated_at"])
        Payment.objects.create(
            renewal=renewals["raiz_server"], amount=renewals["raiz_server"].sale_amount,
            method=PaymentMethod.BANK_TRANSFER, status=Payment.Status.PAID, paid_at=now - timedelta(days=4),
            reference="DEMO-PROCESS-008",
        )

        renewals["joao_backup"].status = Renewal.Status.RENEWED
        renewals["joao_backup"].paid_at = now - timedelta(days=10)
        renewals["joao_backup"].renewed_until = renewals["joao_backup"].due_date + timedelta(days=365)
        renewals["joao_backup"].save()
        Payment.objects.create(
            renewal=renewals["joao_backup"], amount=renewals["joao_backup"].sale_amount,
            method=PaymentMethod.MBWAY, status=Payment.Status.PAID, paid_at=now - timedelta(days=10),
            reference="DEMO-RENEWED-009",
        )

        renewals["aurora_email"].status = Renewal.Status.CANCELLED
        renewals["aurora_email"].save(update_fields=["status", "updated_at"])
        Payment.objects.create(
            renewal=renewals["aurora_email"], amount=renewals["aurora_email"].sale_amount,
            method=PaymentMethod.BANK_TRANSFER, status=Payment.Status.CANCELLED, reference="DEMO-CANCEL-010",
        )

        Payment.objects.create(
            renewal=renewals["joao_domain"], amount=renewals["joao_domain"].sale_amount,
            method=PaymentMethod.CARD, status=Payment.Status.REJECTED, reference="DEMO-REJECT-011",
            notes="Pagamento fictício rejeitado pelo emissor.",
        )
        Payment.objects.create(
            renewal=renewals["raiz_ssl"], amount=renewals["raiz_ssl"].sale_amount,
            method=PaymentMethod.OTHER, status=Payment.Status.REFUNDED, reference="DEMO-REFUND-012",
            notes="Pagamento fictício devolvido.",
        )
        return renewals

    def _notifications(self, renewals, now):
        created = []
        for key, template_type, status in [
            ("horizonte_domain", NotificationTemplate.Type.RENEWAL_15, NotificationLog.Status.SENT),
            ("oficina_domain", NotificationTemplate.Type.OVERDUE, NotificationLog.Status.SENT),
            ("acacia_domain", NotificationTemplate.Type.RENEWAL_30, NotificationLog.Status.DRAFT),
            ("aurora_maintenance", NotificationTemplate.Type.RENEWAL_7, NotificationLog.Status.SCHEDULED),
            ("joao_domain", NotificationTemplate.Type.RENEWAL_30, NotificationLog.Status.FAILED),
        ]:
            notification, _ = create_notification_from_template(renewals[key], template_type)
            notification.status = status
            if status == NotificationLog.Status.SENT:
                notification.sent_at = now - timedelta(days=1)
                notification.delivery_mode = NotificationLog.DeliveryMode.TEST
                notification.attempts_count = 1
            elif status == NotificationLog.Status.SCHEDULED:
                notification.scheduled_for = now + timedelta(hours=2)
            elif status == NotificationLog.Status.FAILED:
                notification.attempts_count = 2
                notification.last_attempt_at = now - timedelta(minutes=15)
                notification.next_retry_at = now + timedelta(minutes=30)
                notification.error_message = "Falha SMTP fictícia para testar repetições."
                notification.delivery_mode = NotificationLog.DeliveryMode.TEST
            notification.save()
            created.append(notification)

        internal = NotificationLog.objects.create(
            client=renewals["clinica_domain"].client,
            service=renewals["clinica_domain"].service,
            renewal=renewals["clinica_domain"],
            channel=NotificationLog.Channel.INTERNAL,
            recipient="Administrador",
            subject="Pagamento recebido — demonstração",
            body="O pagamento fictício foi validado.",
            status=NotificationLog.Status.SENT,
            delivery_mode=NotificationLog.DeliveryMode.INTERNAL,
            sent_at=now - timedelta(days=2),
            attempts_count=1,
        )
        created.append(internal)

        cancelled = NotificationLog.objects.create(
            client=renewals["aurora_email"].client,
            service=renewals["aurora_email"].service,
            renewal=renewals["aurora_email"],
            channel=NotificationLog.Channel.EMAIL,
            recipient=renewals["aurora_email"].client.email,
            subject="Comunicação cancelada — demonstração",
            body="Mensagem fictícia cancelada antes do envio.",
            status=NotificationLog.Status.CANCELLED,
            delivery_mode=NotificationLog.DeliveryMode.TEST,
        )
        created.append(cancelled)

        suppression = EmailSuppression.objects.create(
            email="bloqueado@demo.invalid",
            reason=EmailSuppression.Reason.BOUNCED,
            description="Endereço fictício para testar bloqueios e devoluções.",
            is_active=True,
        )
        suppressed = NotificationLog.objects.create(
            client=renewals["raiz_domain"].client,
            service=renewals["raiz_domain"].service,
            renewal=renewals["raiz_domain"],
            channel=NotificationLog.Channel.EMAIL,
            recipient=suppression.email,
            original_recipient=suppression.email,
            subject="Envio bloqueado — demonstração",
            body="Esta mensagem representa um envio impedido pela lista de supressão.",
            status=NotificationLog.Status.SUPPRESSED,
            delivery_mode=NotificationLog.DeliveryMode.TEST,
            attempts_count=1,
            error_message="Destinatário presente na lista de bloqueios.",
        )
        created.append(suppressed)

        for notification in created:
            if notification.status == NotificationLog.Status.SENT:
                NotificationAttempt.objects.create(
                    notification=notification,
                    result=NotificationAttempt.Result.SUCCESS,
                    recipient_used=notification.recipient if "@" in notification.recipient else "",
                    backend="django.core.mail.backends.console.EmailBackend" if notification.channel == NotificationLog.Channel.EMAIL else "internal",
                    duration_ms=42,
                    message_id=f"demo-{notification.pk}@kreate4web.invalid",
                )
            elif notification.status == NotificationLog.Status.FAILED:
                NotificationAttempt.objects.create(
                    notification=notification,
                    result=NotificationAttempt.Result.FAILED,
                    recipient_used=notification.recipient,
                    backend="smtp.demo.invalid",
                    duration_ms=1200,
                    error_message=notification.error_message,
                )
            elif notification.status == NotificationLog.Status.SUPPRESSED:
                NotificationAttempt.objects.create(
                    notification=notification,
                    result=NotificationAttempt.Result.SUPPRESSED,
                    recipient_used=notification.recipient,
                    backend="suppression-list",
                    duration_ms=1,
                    error_message=notification.error_message,
                )

    def _tasks(self, clients, services, renewals, today):
        InternalTask.objects.get_or_create(
            title="Contactar Oficina do Norte sobre serviços vencidos", client=clients["oficina"], service=services["oficina_hosting"],
            renewal=renewals["oficina_hosting"], task_type=InternalTask.TaskType.CONTACT_CLIENT,
            defaults={"priority": InternalTask.Priority.URGENT, "status": InternalTask.Status.TODO, "due_date": today - timedelta(days=1), "description": "Tarefa fictícia em atraso."},
        )
        InternalTask.objects.create(
            title="Verificar SSL do website Oficina do Norte", client=clients["oficina"], service=services["oficina_hosting"],
            task_type=InternalTask.TaskType.VERIFY_SSL, priority=InternalTask.Priority.HIGH,
            status=InternalTask.Status.IN_PROGRESS, due_date=today + timedelta(days=2), description="Confirmar certificado e cadeia intermédia.",
        )
        InternalTask.objects.create(
            title="Confirmar novo email do Cliente a confirmar", client=clients["sem_email"], service=services["incomplete_domain"],
            task_type=InternalTask.TaskType.CONTACT_CLIENT, priority=InternalTask.Priority.NORMAL,
            status=InternalTask.Status.TODO, due_date=today + timedelta(days=5), description="Completar dados antes de ativar o serviço.",
        )
        InternalTask.objects.create(
            title="Aguardar conteúdos da Loja Aurora", client=clients["aurora"], service=services["aurora_maintenance"],
            task_type=InternalTask.TaskType.OTHER, priority=InternalTask.Priority.LOW,
            status=InternalTask.Status.WAITING, due_date=today + timedelta(days=8), description="Tarefa fictícia a aguardar informação do cliente.",
        )
        InternalTask.objects.create(
            title="Atualização DNS bloqueada", client=clients["horizonte"], service=services["horizonte_domain"],
            task_type=InternalTask.TaskType.UPDATE_DNS, priority=InternalTask.Priority.HIGH,
            status=InternalTask.Status.BLOCKED, due_date=today + timedelta(days=1), description="Falta autorização fictícia do contacto principal.",
        )
        InternalTask.objects.create(
            title="Backup mensal João Martins concluído", client=clients["joao"], service=services["joao_backup"],
            task_type=InternalTask.TaskType.CREATE_BACKUP, priority=InternalTask.Priority.NORMAL,
            status=InternalTask.Status.DONE, due_date=today - timedelta(days=2), description="Tarefa fictícia concluída.",
        )
        InternalTask.objects.create(
            title="Verificação técnica cancelada", client=clients["associacao"], service=services["raiz_hosting"],
            task_type=InternalTask.TaskType.TECHNICAL_CHECK, priority=InternalTask.Priority.LOW,
            status=InternalTask.Status.CANCELLED, due_date=today - timedelta(days=10), description="Tarefa fictícia cancelada.",
        )


    def _support_and_documents(self, clients, services, renewals, today, now):
        ticket_1 = SupportTicket.objects.create(
            client=clients["oficina"],
            service=services["oficina_hosting"],
            subject="Website temporariamente indisponível",
            category=SupportTicket.Category.TECHNICAL,
            priority=SupportTicket.Priority.URGENT,
            status=SupportTicket.Status.OPEN,
            channel=SupportTicket.Channel.EMAIL,
            description="Pedido fictício: o cliente indica que o website não abre desde esta manhã.",
            due_date=today,
            last_activity_at=now - timedelta(minutes=35),
        )
        SupportMessage.objects.create(
            ticket=ticket_1,
            author_type=SupportMessage.AuthorType.CLIENT,
            author_name="Oficina do Norte",
            author_email="geral@oficina.demo.invalid",
            message="O website apresenta um erro ao abrir. Podem verificar?",
        )
        SupportMessage.objects.create(
            ticket=ticket_1,
            author_type=SupportMessage.AuthorType.STAFF,
            author_name="Administração",
            message="A verificar certificado SSL e estado da conta de alojamento.",
            is_internal=True,
        )

        ticket_2 = SupportTicket.objects.create(
            client=clients["clinica"],
            service=services["clinica_maintenance"],
            subject="Alterar horário na página de contactos",
            category=SupportTicket.Category.CHANGE,
            priority=SupportTicket.Priority.NORMAL,
            status=SupportTicket.Status.IN_PROGRESS,
            channel=SupportTicket.Channel.PORTAL,
            description="Pedido fictício de pequena alteração ao website.",
            due_date=today + timedelta(days=3),
            last_activity_at=now - timedelta(hours=3),
        )
        SupportMessage.objects.create(
            ticket=ticket_2,
            author_type=SupportMessage.AuthorType.CLIENT,
            author_name="Clínica Sorriso",
            message="Pretendemos alterar o horário de sábado para 09:00–13:00.",
        )

        ticket_3 = SupportTicket.objects.create(
            client=clients["acacia"],
            service=services["acacia_domain"],
            subject="Confirmar dados para renovação",
            category=SupportTicket.Category.DOMAIN,
            priority=SupportTicket.Priority.HIGH,
            status=SupportTicket.Status.WAITING_CLIENT,
            channel=SupportTicket.Channel.INTERNAL,
            description="Pedido fictício visível no portal do cliente.",
            due_date=today + timedelta(days=5),
            last_activity_at=now - timedelta(days=1),
        )
        SupportMessage.objects.create(
            ticket=ticket_3,
            author_type=SupportMessage.AuthorType.STAFF,
            author_name="Administração",
            message="Foi solicitado ao cliente que confirme o contacto de faturação.",
        )

        contract = Document.objects.create(
            title="Contrato de serviços — Acácia Studio (DEMO)",
            category=Document.Category.CONTRACT,
            client=clients["acacia"],
            service=services["acacia_hosting"],
            issued_on=today - timedelta(days=365),
            expires_on=today + timedelta(days=20),
            reference="DEMO-CONTRATO-001",
            visible_to_client=True,
            notes="Documento fictício criado para testar o download protegido no portal.",
        )
        contract.file.save(
            "contrato-kreate4web-demo.txt",
            ContentFile(b"Kreate4Web - documento ficticio para testes locais.\n"),
            save=True,
        )
        Document.objects.create(
            title="Fatura de renovação — Clínica Sorriso (DEMO)",
            category=Document.Category.INVOICE,
            client=clients["clinica"],
            service=services["clinica_domain"],
            issued_on=today - timedelta(days=2),
            reference="FT DEMO/2026/001",
            visible_to_client=True,
            notes="Documento fictício sem ficheiro.",
        )
        Document.objects.create(
            title="Relatório técnico SSL — Oficina do Norte (DEMO)",
            category=Document.Category.TECHNICAL,
            client=clients["oficina"],
            service=services["oficina_hosting"],
            support_ticket=ticket_1,
            issued_on=today,
            reference="DEMO-TEC-001",
            visible_to_client=False,
            notes="Relatório interno fictício.",
        )

    def _complete_demo_coverage(self, clients, services, renewals, today, now):
        """Preenche os módulos que poderiam ficar sem dados num arranque de demonstração."""
        # Contactos e notas em todos os clientes, para testar inlines e pesquisas.
        for index, (key, client) in enumerate(clients.items(), start=1):
            if not client.contacts.exists():
                ClientContact.objects.create(
                    client=client,
                    name=f"Contacto {client.name}",
                    role="Responsável de demonstração",
                    email=client.email or f"contacto{index}@demo.invalid",
                    phone=client.phone,
                    is_primary=True,
                    receives_billing=index % 2 == 0,
                    receives_technical=index % 2 == 1,
                )
            if not client.notes.exists():
                ClientNote.objects.create(
                    client=client,
                    title="Nota de demonstração",
                    body="Informação fictícia criada para validar a ficha e o histórico do cliente.",
                    is_important=index % 3 == 0,
                )

        # Servidores com estados distintos.
        provider = Provider.objects.filter(provider_type=Provider.ProviderType.SERVER).first()
        for name, hostname, ip, status, location in [
            ("Servidor Porto 02 (DEMO)", "por02.demo.invalid", "192.0.2.22", Server.Status.DEGRADED, "Porto"),
            ("Servidor Manutenção (DEMO)", "maint.demo.invalid", "192.0.2.30", Server.Status.MAINTENANCE, "Madrid"),
            ("Servidor Offline (DEMO)", "offline.demo.invalid", "192.0.2.40", Server.Status.OFFLINE, "Arquivo"),
            ("Servidor Desativado (DEMO)", "retired.demo.invalid", "192.0.2.50", Server.Status.RETIRED, "Histórico"),
        ]:
            Server.objects.get_or_create(
                name=name,
                defaults={
                    "provider": provider,
                    "hostname": hostname,
                    "ip_address": ip,
                    "location": location,
                    "control_panel": "cPanel",
                    "status": status,
                    "notes": "Servidor inteiramente fictício para testar estados e cores.",
                },
            )

        # Tickets e mensagens com os restantes estados/categorias.
        ticket_specs = [
            ("horizonte", "horizonte_hosting", "Dúvida sobre fatura", SupportTicket.Category.BILLING, SupportTicket.Status.WAITING_INTERNAL, SupportTicket.Priority.NORMAL),
            ("aurora", "aurora_email", "Conta de email não sincroniza", SupportTicket.Category.EMAIL, SupportTicket.Status.REOPENED, SupportTicket.Priority.HIGH),
            ("joao", "joao_website", "Revisão da galeria", SupportTicket.Category.WEBSITE, SupportTicket.Status.RESOLVED, SupportTicket.Priority.LOW),
            ("associacao", "raiz_hosting", "Informação sobre alojamento", SupportTicket.Category.HOSTING, SupportTicket.Status.CLOSED, SupportTicket.Priority.NORMAL),
            ("sem_email", "incomplete_domain", "Pedido geral por confirmar", SupportTicket.Category.OTHER, SupportTicket.Status.OPEN, SupportTicket.Priority.LOW),
        ]
        for client_key, service_key, subject, category, final_status, priority in ticket_specs:
            ticket = SupportTicket.objects.create(
                client=clients[client_key],
                service=services[service_key],
                subject=subject,
                category=category,
                priority=priority,
                status=SupportTicket.Status.OPEN,
                channel=SupportTicket.Channel.PORTAL,
                description="Ticket totalmente fictício criado para testar o portal e o backoffice.",
                due_date=today + timedelta(days=3),
                client_visible=True,
                last_activity_at=now - timedelta(hours=2),
            )
            SupportMessage.objects.create(
                ticket=ticket,
                author_type=SupportMessage.AuthorType.CLIENT,
                author_name=clients[client_key].name,
                author_email=clients[client_key].email or "cliente@demo.invalid",
                message="Mensagem fictícia inicial do cliente.",
            )
            SupportMessage.objects.create(
                ticket=ticket,
                author_type=SupportMessage.AuthorType.STAFF,
                author_name="Equipa Kreate4Web",
                message="Resposta pública fictícia da equipa para validar várias mensagens.",
                read_by_staff_at=now,
            )
            ticket.status = final_status
            if final_status in {SupportTicket.Status.RESOLVED, SupportTicket.Status.CLOSED}:
                ticket.closed_at = now - timedelta(hours=1)
                ticket.resolution_note = "Resolução fictícia registada para testes."
            ticket.save()

        # Documentos de todas as categorias, incluindo ficheiros protegidos.
        document_specs = [
            (Document.Category.RECEIPT, "Recibo DEMO — Horizonte", "horizonte", "horizonte_domain", True),
            (Document.Category.PAYMENT_PROOF, "Comprovativo DEMO — Acácia", "acacia", "acacia_domain", True),
            (Document.Category.DOMAIN, "Comprovativo de registo de domínio — João", "joao", "joao_domain", True),
            (Document.Category.REPORT, "Relatório mensal — Raiz Digital", "associacao", "raiz_maintenance" if "raiz_maintenance" in services else "raiz_hosting", True),
            (Document.Category.OTHER, "Documento interno — Loja Aurora", "aurora", "aurora_other", False),
        ]
        for index, (category, title, client_key, service_key, visible) in enumerate(document_specs, start=10):
            document = Document.objects.create(
                title=title,
                category=category,
                client=clients[client_key],
                service=services[service_key],
                issued_on=today - timedelta(days=index),
                reference=f"DEMO-DOC-{index:03d}",
                visible_to_client=visible,
                notes="Documento fictício para testar categorias, permissões e downloads.",
            )
            document.file.save(
                f"documento-demo-{index}.txt",
                ContentFile(f"Kreate4Web — {title}\nConteúdo estritamente fictício.\n".encode("utf-8")),
                save=True,
            )

        # Um comprovativo associado diretamente a um pagamento.
        payment = Payment.objects.filter(status=Payment.Status.VALIDATING).first()
        if payment and not payment.proof:
            payment.proof.save(
                "comprovativo-pagamento-demo.txt",
                ContentFile(b"Comprovativo ficticio para testes locais.\n"),
                save=True,
            )
        if payment:
            document = Document.objects.create(
                title="Comprovativo associado ao pagamento (DEMO)",
                category=Document.Category.PAYMENT_PROOF,
                client=payment.client,
                service=payment.renewal.service,
                payment=payment,
                issued_on=today,
                reference="DEMO-PROOF-LINK",
                visible_to_client=True,
                notes="Valida a relação Documento → Pagamento.",
            )
            document.file.save("comprovativo-associado-demo.txt", ContentFile(b"Ficheiro ficticio.\n"), save=True)

        # Lotes e linhas de importação para que o centro de dados não fique vazio.
        review_batch = ImportBatch.objects.create(
            title="Importação de demonstração — em revisão",
            original_filename="alojamentos-demo.xlsx",
            status=ImportBatch.Status.REVIEW,
            selected_sheets=["Alojamentos", "Dominios"],
            total_rows=3,
            valid_rows=1,
            warning_rows=1,
            error_rows=1,
            analysed_at=now - timedelta(hours=4),
            notes="Lote fictício; não contém dados do Excel real.",
        )
        review_batch.source_file.save("alojamentos-demo.xlsx", ContentFile(b"DEMO XLSX PLACEHOLDER"), save=True)
        rows = [
            (2, ImportRow.RecordType.HOSTING, ImportRow.Severity.VALID, ImportRow.Decision.IMPORT, "Alojamento DEMO válido"),
            (3, ImportRow.RecordType.DOMAIN, ImportRow.Severity.WARNING, ImportRow.Decision.PENDING, "dominio-e-outro.pt"),
            (4, ImportRow.RecordType.UNKNOWN, ImportRow.Severity.ERROR, ImportRow.Decision.IGNORE, "#REF!"),
        ]
        for row_number, record_type, severity, decision, label in rows:
            ImportRow.objects.create(
                batch=review_batch,
                sheet_name="Alojamentos",
                row_number=row_number,
                record_type=record_type,
                source_label=label,
                raw_data={"coluna_a": label},
                normalized_data={"label": label},
                severity=severity,
                decision=decision,
                issues=[] if severity == ImportRow.Severity.VALID else ["Exemplo fictício de validação."],
                proposed_client_name="Acácia Studio" if row_number == 2 else "",
                proposed_service_name=label,
                proposed_sale_price=Decimal("100.00") if row_number == 2 else None,
                processing_status=ImportRow.ProcessingStatus.PENDING,
            )

        imported_batch = ImportBatch.objects.create(
            title="Importação de demonstração — concluída",
            original_filename="clientes-demo.xlsx",
            status=ImportBatch.Status.IMPORTED,
            selected_sheets=["Dominios"],
            total_rows=1,
            valid_rows=1,
            analysed_at=now - timedelta(days=3),
            simulated_at=now - timedelta(days=3, hours=-1),
            imported_at=now - timedelta(days=2),
            import_summary={"clients_reused": 1, "services_created": 0},
            notes="Lote fictício para testar auditoria e histórico.",
        )
        imported_batch.source_file.save("clientes-demo.xlsx", ContentFile(b"DEMO IMPORTED XLSX"), save=True)
        imported_row = ImportRow.objects.create(
            batch=imported_batch,
            sheet_name="Dominios",
            row_number=2,
            record_type=ImportRow.RecordType.DOMAIN,
            source_label="acaciastudio.pt",
            raw_data={"dominio": "acaciastudio.pt"},
            normalized_data={"domain_name": "acaciastudio.pt"},
            severity=ImportRow.Severity.VALID,
            decision=ImportRow.Decision.LINK,
            processing_status=ImportRow.ProcessingStatus.LINKED,
            processing_message="Associado ao cliente e domínio fictícios existentes.",
            processed_at=now - timedelta(days=2),
            proposed_client_name=clients["acacia"].name,
            proposed_domain_name="acaciastudio.pt",
        )
        content_type = ContentType.objects.get_for_model(Client)
        ImportedObject.objects.create(
            batch=imported_batch,
            row=imported_row,
            content_type=content_type,
            object_id=clients["acacia"].pk,
            action=ImportedObject.Action.REUSED,
            label=str(clients["acacia"]),
            object_updated_at=clients["acacia"].updated_at,
        )

        # Auditoria de utilização da nova biblioteca V7.
        reusable = ReusableTemplate.objects.order_by("pk").first()
        form = FormTemplate.objects.order_by("pk").first()
        if reusable:
            TemplateUsage.objects.create(
                reusable_template=reusable,
                action=TemplateUsage.Action.COPY,
                context={"client": clients["acacia"].name, "demo": True},
            )
            reusable.mark_used()
        if form:
            TemplateUsage.objects.create(
                form_template=form,
                action=TemplateUsage.Action.PREVIEW,
                context={"client": clients["clinica"].name, "demo": True},
            )
