from __future__ import annotations

import uuid
from decimal import Decimal

from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils import timezone

from core.choices import ServiceType
from core.models import TimeStampedModel


def generate_import_code():
    return f"IMP-{timezone.localdate():%Y}-{uuid.uuid4().hex[:6].upper()}"


class ImportBatch(TimeStampedModel):
    class Status(models.TextChoices):
        UPLOADED = "uploaded", "Carregado"
        ANALYSING = "analysing", "Em análise"
        REVIEW = "review", "Em revisão"
        READY = "ready", "Pronto para simular"
        SIMULATED = "simulated", "Simulado"
        IMPORTING = "importing", "A importar"
        IMPORTED = "imported", "Importado"
        ROLLED_BACK = "rolled_back", "Anulado"
        FAILED = "failed", "Falhou"
        CANCELLED = "cancelled", "Cancelado"

    code = models.CharField("Código", max_length=24, blank=True, default=generate_import_code, db_index=True)
    title = models.CharField("Designação", max_length=180, blank=True)
    source_file = models.FileField("Ficheiro Excel", upload_to="imports/%Y/%m/")
    original_filename = models.CharField("Nome original", max_length=255, blank=True)
    status = models.CharField("Estado", max_length=20, choices=Status.choices, default=Status.UPLOADED, db_index=True)
    selected_sheets = models.JSONField("Folhas selecionadas", default=list, blank=True)
    total_rows = models.PositiveIntegerField("Linhas analisadas", default=0)
    valid_rows = models.PositiveIntegerField("Linhas válidas", default=0)
    warning_rows = models.PositiveIntegerField("Com avisos", default=0)
    error_rows = models.PositiveIntegerField("Com erros", default=0)
    ignored_rows = models.PositiveIntegerField("Ignoradas", default=0)
    analysed_at = models.DateTimeField("Analisado em", null=True, blank=True)
    simulated_at = models.DateTimeField("Simulado em", null=True, blank=True)
    imported_at = models.DateTimeField("Importado em", null=True, blank=True)
    rolled_back_at = models.DateTimeField("Anulado em", null=True, blank=True)
    simulation_summary = models.JSONField("Resumo da simulação", default=dict, blank=True)
    import_summary = models.JSONField("Resumo da importação", default=dict, blank=True)
    rollback_summary = models.JSONField("Resumo da anulação", default=dict, blank=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="import_batches",
        verbose_name="Criado por",
    )
    executed_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="executed_import_batches",
        verbose_name="Executado por",
    )
    notes = models.TextField("Notas", blank=True)
    error_message = models.TextField("Erro da análise/importação", blank=True)

    class Meta:
        verbose_name = "Importação Excel"
        verbose_name_plural = "Importações Excel"
        ordering = ["-created_at"]

    def save(self, *args, **kwargs):
        if not self.code:
            self.code = generate_import_code()
        super().save(*args, **kwargs)

    @property
    def progress_percentage(self):
        if not self.total_rows:
            return 0
        reviewed = self.rows.exclude(decision=ImportRow.Decision.PENDING).count()
        return round((reviewed / self.total_rows) * 100)

    @property
    def pending_rows(self):
        return self.rows.filter(decision=ImportRow.Decision.PENDING).count()

    @property
    def can_rollback(self):
        return self.status == self.Status.IMPORTED and self.imported_objects.filter(action=ImportedObject.Action.CREATED).exists()

    def __str__(self):
        return self.title or self.original_filename or self.code


class ImportRow(TimeStampedModel):
    class RecordType(models.TextChoices):
        HOSTING = "hosting", "Alojamento"
        DOMAIN = "domain", "Domínio"
        WEBSITE = "website", "Projeto web"
        TECHNICAL = "technical", "Dado técnico"
        PAYMENT = "payment", "Pagamento histórico"
        UNKNOWN = "unknown", "Por classificar"

    class Severity(models.TextChoices):
        VALID = "valid", "Válida"
        WARNING = "warning", "Com aviso"
        ERROR = "error", "Com erro"

    class Decision(models.TextChoices):
        PENDING = "pending", "Por decidir"
        IMPORT = "import", "Importar"
        IGNORE = "ignore", "Ignorar"
        ARCHIVE = "archive", "Arquivar como histórico"
        LINK = "link", "Associar a registo existente"

    class ProcessingStatus(models.TextChoices):
        PENDING = "pending", "Não processada"
        SIMULATED = "simulated", "Simulada"
        CREATED = "created", "Criada"
        LINKED = "linked", "Associada"
        SKIPPED = "skipped", "Ignorada"
        FAILED = "failed", "Falhou"
        ROLLED_BACK = "rolled_back", "Anulada"

    batch = models.ForeignKey(ImportBatch, on_delete=models.CASCADE, related_name="rows", verbose_name="Importação")
    sheet_name = models.CharField("Folha", max_length=100, db_index=True)
    row_number = models.PositiveIntegerField("Linha")
    record_type = models.CharField("Tipo detetado", max_length=20, choices=RecordType.choices, default=RecordType.UNKNOWN, db_index=True)
    source_label = models.CharField("Identificação original", max_length=300, blank=True)
    raw_data = models.JSONField("Dados originais", default=dict, blank=True)
    normalized_data = models.JSONField("Dados normalizados", default=dict, blank=True)
    severity = models.CharField("Validação", max_length=15, choices=Severity.choices, default=Severity.WARNING, db_index=True)
    decision = models.CharField("Decisão", max_length=15, choices=Decision.choices, default=Decision.PENDING, db_index=True)
    processing_status = models.CharField(
        "Processamento", max_length=20, choices=ProcessingStatus.choices, default=ProcessingStatus.PENDING, db_index=True
    )
    processing_message = models.TextField("Resultado do processamento", blank=True)
    processed_at = models.DateTimeField("Processada em", null=True, blank=True)
    issues = models.JSONField("Problemas encontrados", default=list, blank=True)

    proposed_client_name = models.CharField("Cliente proposto", max_length=180, blank=True)
    proposed_service_name = models.CharField("Serviço proposto", max_length=180, blank=True)
    proposed_domain_name = models.CharField("Domínio proposto", max_length=253, blank=True)
    proposed_service_type = models.CharField("Tipo de serviço proposto", max_length=20, choices=ServiceType.choices, blank=True)
    proposed_renewal_date = models.DateField("Data de renovação proposta", null=True, blank=True)
    proposed_cost_price = models.DecimalField("Custo proposto", max_digits=12, decimal_places=2, null=True, blank=True)
    proposed_sale_price = models.DecimalField("Valor a cobrar proposto", max_digits=12, decimal_places=2, null=True, blank=True)
    proposed_status = models.CharField("Estado original / proposto", max_length=100, blank=True)
    proposed_notes = models.TextField("Notas de revisão", blank=True)

    duplicate_key = models.CharField("Chave de duplicação", max_length=300, blank=True, db_index=True)
    target_model = models.CharField("Modelo de destino", max_length=100, blank=True)
    target_object_id = models.CharField("Registo existente", max_length=100, blank=True)

    class Meta:
        verbose_name = "Linha de importação"
        verbose_name_plural = "Linhas de importação"
        ordering = ["batch", "sheet_name", "row_number"]
        constraints = [
            models.UniqueConstraint(fields=["batch", "sheet_name", "row_number"], name="unique_import_sheet_row")
        ]
        indexes = [
            models.Index(fields=["batch", "severity", "decision"]),
            models.Index(fields=["record_type", "duplicate_key"]),
            models.Index(fields=["batch", "processing_status"]),
        ]

    @property
    def issues_text(self):
        return " · ".join(self.issues) if self.issues else "Sem problemas detetados"

    def __str__(self):
        return f"{self.sheet_name} · linha {self.row_number} · {self.source_label or self.get_record_type_display()}"


class ImportedObject(TimeStampedModel):
    class Action(models.TextChoices):
        CREATED = "created", "Criado"
        LINKED = "linked", "Associado"
        REUSED = "reused", "Reutilizado"

    batch = models.ForeignKey(ImportBatch, on_delete=models.CASCADE, related_name="imported_objects", verbose_name="Importação")
    row = models.ForeignKey(
        ImportRow, on_delete=models.SET_NULL, null=True, blank=True, related_name="imported_objects", verbose_name="Linha"
    )
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, verbose_name="Tipo de registo")
    object_id = models.PositiveBigIntegerField("ID do registo")
    content_object = GenericForeignKey("content_type", "object_id")
    action = models.CharField("Ação", max_length=15, choices=Action.choices, default=Action.CREATED, db_index=True)
    label = models.CharField("Identificação", max_length=300, blank=True)
    object_updated_at = models.DateTimeField("Atualização no momento da importação", null=True, blank=True)
    rollback_blocked_reason = models.TextField("Motivo de bloqueio da anulação", blank=True)

    class Meta:
        verbose_name = "Registo importado"
        verbose_name_plural = "Registos importados"
        ordering = ["batch", "created_at"]
        constraints = [
            models.UniqueConstraint(
                fields=["batch", "content_type", "object_id", "action"], name="unique_imported_object_action"
            )
        ]
        indexes = [models.Index(fields=["batch", "action"]), models.Index(fields=["content_type", "object_id"])]

    def __str__(self):
        return self.label or f"{self.content_type} #{self.object_id}"
