import os
from urllib.parse import urlparse
import requests
from babel.dates import format_date
from django.core.files.base import ContentFile
from django.db import models, transaction
from django.core.exceptions import ValidationError
import fitz  # PyMuPDF
from asgiref.sync import async_to_sync


class TipoDiarioOficial(models.Model):
    nome = models.CharField(max_length=100, unique=True)

    def __str__(self):
        return self.nome

    class Meta:
        verbose_name_plural = "Tipos de Diários Oficiais"


class DiarioOficial(models.Model):
    data = models.DateField()
    arquivo = models.FileField(upload_to="diarios_oficiais/", blank=True, null=True)
    tipo = models.ForeignKey(
        TipoDiarioOficial,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        related_name="diarios",
    )
    numero = models.CharField(max_length=20, unique=True)
    link = models.URLField(blank=True, null=True, unique=True)

    def save(self, *args, **kwargs):
        updated = False
        super().save(*args, **kwargs)

        if self.link and not self.arquivo:
            self._download_pdf_from_link()
            updated = True

        if self.arquivo and not self.paginas.exists():
            self._extract_pdf_pages()
            updated = True

        if updated:
            super().save(*args, **kwargs)

    def clean(self):
        super().clean()
        if not self.arquivo and not self.link:
            raise ValidationError("Informe um arquivo ou um link para o Diário.")

    def _validar_link(self):
        if not self.link.lower().endswith(".pdf"):
            raise ValidationError("O link deve apontar para um arquivo PDF.")

    def _download_pdf_from_link(self):
        try:
            response = requests.get(self.link)
            response.raise_for_status()

            parsed_url = urlparse(self.link)
            file_name = os.path.basename(parsed_url.path) or f"diario_{self.numero}.pdf"

            self.arquivo.save(file_name, ContentFile(response.content), save=True)
        except requests.RequestException as e:
            raise ValidationError(f"Não foi possível baixar o PDF: {e}")

    def _extract_pdf_pages(self):
        try:
            # Salvar temporariamente o PDF para abrir com o PyMuPDF
            with self.arquivo.open("rb") as pdf_file:
                temp_pdf_path = f"/tmp/diario_{self.id}.pdf"
                with open(temp_pdf_path, "wb") as temp_file:
                    temp_file.write(pdf_file.read())

            # Abrir e processar com fitz
            doc = fitz.open(temp_pdf_path)
            self._process_pdf_pages(doc)
            doc.close()

            # Remover arquivo temporário
            os.remove(temp_pdf_path)

        except Exception as pdf_error:
            raise ValidationError(f"Não foi possível processar o PDF: {pdf_error}")

    def _process_pdf_pages(self, doc):
        with transaction.atomic():
            self.paginas.all().delete()

            for i, page in enumerate(doc):
                try:
                    blocks = page.get_text("blocks")
                    # Ordenar os blocos por coordenadas (y, x) para manter a ordem de leitura
                    blocks.sort(key=lambda b: (b[1], b[0]))
                    page_text = ""
                    for block in blocks:
                        text = block[4].strip()
                        if text:
                            page_text += text + "\n"

                    # Crucial: Remove NULL bytes from the extracted text
                    # PostgreSQL text fields cannot contain NUL (0x00) bytes
                    cleaned_text = page_text.strip().replace('\x00', '')

                    if cleaned_text:
                        PageDiarioOficial.objects.create(
                            diario=self,
                            numero=i + 1,
                            conteudo=cleaned_text,
                        )
                    else:
                        PageDiarioOficial.objects.create(
                            diario=self,
                            numero=i + 1,
                            conteudo="[Conteúdo não extraído ou vazio]",
                        )

                except Exception as page_error:
                    PageDiarioOficial.objects.create(
                        diario=self,
                        numero=i + 1,
                        conteudo=f"[Erro na extração do texto: {str(page_error)}]",
                    )
                    print(f"Erro ao processar a página {i+1} no Diario ID {self.id}: {page_error}")

    @property
    def data_formatada(self):
        return format_date(self.data, format="long", locale="pt_BR")

    @property
    def is_online(self):
        return bool(self.link)

    def __str__(self):
        tipo_nome = self.tipo.nome if self.tipo else "Sem Tipo"
        return f"Diário {tipo_nome} nº {self.numero}, {self.data_formatada}"

    class Meta:
        constraints = [models.UniqueConstraint(fields=["numero"], name="unique_numero")]
        verbose_name_plural = "Diários Oficiais"


class PageDiarioOficial(models.Model):
    diario = models.ForeignKey(
        DiarioOficial, on_delete=models.CASCADE, related_name="paginas"
    )
    layout_duas_colunas = models.BooleanField(default=False)
    numero = models.PositiveIntegerField()
    conteudo = models.TextField()

    class Meta:
        unique_together = ("diario", "numero")
        verbose_name = "Página de Diário Oficial"
        verbose_name_plural = "Páginas de Diários Oficiais"

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        if self.diario:
            self.diario.save()

    def __str__(self):
        return f"Página {self.numero} do Diário {self.diario.numero}"

