import os
import tempfile
from pathlib import Path
from typing import Optional, Union, List
import numpy as np

# Processamento de imagem
try:
    import cv2
    import pytesseract
    from pdf2image import convert_from_path
    from PIL import Image
except ImportError as e:
    raise ImportError("Dependências obrigatórias: opencv-python, pillow, pdf2image, pytesseract") from e

# Backends opcionais
try:
    import easyocr
    EASYOCR_AVAILABLE = True
except ImportError:
    EASYOCR_AVAILABLE = False

try:
    import paddleocr
    PADDLEOCR_AVAILABLE = True
except ImportError:
    PADDLEOCR_AVAILABLE = False


class OCRExtractor:
    """
    Extrator de texto com suporte a múltiplos backends e pré-processamento.
    """

    def __init__(
        self,
        backend: str = "tesseract",
        lang: str = "por",
        psm: int = 6,
        dpi: int = 300,
        preprocess: bool = True,
        tessdata_path: Optional[str] = None,
        easyocr_reader_kwargs: Optional[dict] = None,
        paddleocr_kwargs: Optional[dict] = None,
    ):
        """
        Args:
            backend: 'tesseract', 'easyocr' ou 'paddleocr'
            lang: código do idioma (para tesseract 'por', easyocr 'pt', paddleocr 'pt')
            psm: Page Segmentation Mode do Tesseract (ignorado nos outros)
            dpi: resolução para conversão de PDF
            preprocess: se deve aplicar pré-processamento nas imagens
            tessdata_path: caminho personalizado para tessdata (Tesseract)
            easyocr_reader_kwargs: kwargs extras para easyocr.Reader
            paddleocr_kwargs: kwargs extras para PaddleOCR
        """
        self.backend = backend.lower()
        self.lang = lang
        self.psm = psm
        self.dpi = dpi
        self.preprocess = preprocess

        # Configuração do Tesseract
        if self.backend == "tesseract":
            if tessdata_path:
                pytesseract.pytesseract.tesseract_cmd = tessdata_path
            # Verifica se o comando tesseract existe
            try:
                pytesseract.get_tesseract_version()
            except Exception as e:
                raise RuntimeError("Tesseract não está instalado ou não foi encontrado.") from e

        # Configuração do EasyOCR
        elif self.backend == "easyocr":
            if not EASYOCR_AVAILABLE:
                raise ImportError("EasyOCR não instalado. Execute: pip install easyocr")
            reader_kwargs = easyocr_reader_kwargs or {}
            # Mapeia código de idioma: 'por' -> ['pt']
            lang_list = ['pt'] if self.lang == 'por' else [self.lang]
            self.easyocr_reader = easyocr.Reader(lang_list, **reader_kwargs)

        # Configuração do PaddleOCR
        elif self.backend == "paddleocr":
            if not PADDLEOCR_AVAILABLE:
                raise ImportError("PaddleOCR não instalado. Execute: pip install paddleocr")
            ocr_kwargs = paddleocr_kwargs or {}
            # PaddleOCR usa 'pt' para português
            lang_pp = 'pt' if self.lang == 'por' else self.lang
            self.paddleocr_engine = paddleocr.PaddleOCR(lang=lang_pp, **ocr_kwargs)

        else:
            raise ValueError(f"Backend desconhecido: {backend}")

    def preprocess_image(self, image: np.ndarray) -> np.ndarray:
        """
        Melhora a imagem para OCR.
        """
        # Converte para escala de cinza se necessário
        if len(image.shape) == 3:
            gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        else:
            gray = image

        # Redimensiona se muito pequena
        h, w = gray.shape
        if w < 1000 or h < 1000:
            scale = max(2, 1500 / w)
            new_w = int(w * scale)
            new_h = int(h * scale)
            gray = cv2.resize(gray, (new_w, new_h), interpolation=cv2.INTER_CUBIC)

        # Redução de ruído preservando bordas
        denoised = cv2.bilateralFilter(gray, 9, 75, 75)

        # Binarização adaptativa
        binary = cv2.adaptiveThreshold(
            denoised, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
            cv2.THRESH_BINARY, 31, 10
        )

        # Remoção de pequenos ruídos (opening)
        kernel = np.ones((1, 1), np.uint8)
        opened = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)

        # Dilatação leve para conectar caracteres quebrados
        kernel = np.ones((1, 2), np.uint8)
        dilated = cv2.dilate(opened, kernel, iterations=1)

        return dilated

    def _image_to_text_tesseract(self, image: np.ndarray) -> str:
        """Usa Tesseract para extrair texto de uma imagem numpy (BGR)."""
        if self.preprocess:
            img = self.preprocess_image(image)
        else:
            if len(image.shape) == 3:
                img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
            else:
                img = image

        config = f'--psm {self.psm} -c preserve_interword_spaces=1'
        text = pytesseract.image_to_string(img, lang=self.lang, config=config)
        return text.strip()

    def _image_to_text_easyocr(self, image: np.ndarray) -> str:
        """Usa EasyOCR para extrair texto."""
        # EasyOCR espera imagem RGB (ou caminho). Passamos diretamente array RGB.
        if len(image.shape) == 3 and image.shape[2] == 3:
            # Converte BGR (OpenCV) para RGB
            img_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        else:
            img_rgb = image

        # Se pré-processamento estiver ativado, aplicamos (mas EasyOCR já tem modelos robustos)
        if self.preprocess:
            # Para EasyOCR, pode ser interessante apenas redimensionar e converter para RGB
            # O pré-processamento agressivo pode prejudicar, então vamos aplicar só redimensionamento se necessário
            h, w = img_rgb.shape[:2]
            if w < 1000 or h < 1000:
                scale = max(2, 1500 / w)
                new_w = int(w * scale)
                new_h = int(h * scale)
                img_rgb = cv2.resize(img_rgb, (new_w, new_h), interpolation=cv2.INTER_CUBIC)

        result = self.easyocr_reader.readtext(img_rgb, detail=0, paragraph=True)
        return '\n'.join(result).strip()

    def _image_to_text_paddleocr(self, image: np.ndarray) -> str:
        """Usa PaddleOCR para extrair texto."""
        # PaddleOCR aceita numpy array (BGR ou RGB?) – melhor passar RGB.
        if len(image.shape) == 3 and image.shape[2] == 3:
            img_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        else:
            img_rgb = image

        # PaddleOCR espera caminho ou array. Vamos usar array.
        result = self.paddleocr_engine.ocr(img_rgb, cls=False)
        # Resultado é lista de páginas? Para imagem única, result[0] é lista de detecções.
        texts = []
        if result and result[0]:
            for line in result[0]:
                texts.append(line[1][0])  # line[1] = (texto, confiança)
        return '\n'.join(texts).strip()

    def extract_from_image(self, image_path: str) -> str:
        """Extrai texto de um arquivo de imagem."""
        image = cv2.imread(image_path)
        if image is None:
            raise ValueError(f"Não foi possível carregar imagem: {image_path}")

        if self.backend == "tesseract":
            return self._image_to_text_tesseract(image)
        elif self.backend == "easyocr":
            return self._image_to_text_easyocr(image)
        elif self.backend == "paddleocr":
            return self._image_to_text_paddleocr(image)
        else:
            raise RuntimeError(f"Backend {self.backend} não implementado para imagem.")

    def extract_from_pdf(self, pdf_path: str) -> str:
        """Converte PDF para imagens e extrai texto de todas as páginas."""
        images = convert_from_path(pdf_path, dpi=self.dpi)
        full_text = []
        for i, pil_img in enumerate(images):
            # Converte PIL para array OpenCV (BGR)
            open_cv_img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)

            if self.backend == "tesseract":
                page_text = self._image_to_text_tesseract(open_cv_img)
            elif self.backend == "easyocr":
                page_text = self._image_to_text_easyocr(open_cv_img)
            elif self.backend == "paddleocr":
                page_text = self._image_to_text_paddleocr(open_cv_img)
            else:
                raise RuntimeError(f"Backend {self.backend} não implementado para PDF.")

            if page_text:
                full_text.append(f"--- Página {i+1} ---\n{page_text}")

        return "\n\n".join(full_text).strip()

    def extract_from_file(self, file_path: str) -> str:
        """Detecta tipo de arquivo e extrai texto."""
        ext = Path(file_path).suffix.lower()
        if ext == '.pdf':
            return self.extract_from_pdf(file_path)
        elif ext in ('.png', '.jpg', '.jpeg', '.tiff', '.bmp'):
            return self.extract_from_image(file_path)
        else:
            raise ValueError(f"Formato não suportado: {ext}")


# Função de compatibilidade com o carregador existente
def load_document(file_path: str, backend: str = "tesseract", **kwargs) -> str:
    """
    Carrega e extrai texto de um documento usando o OCR especificado.
    Args:
        file_path: caminho do arquivo
        backend: 'tesseract', 'easyocr' ou 'paddleocr'
        **kwargs: passados para OCRExtractor
    Returns:
        Texto extraído
    """
    extractor = OCRExtractor(backend=backend, **kwargs)
    return extractor.extract_from_file(file_path)

def extract_text_from_image(image_path: str, backend: str = "tesseract", **kwargs) -> str:
    extractor = OCRExtractor(backend=backend, **kwargs)
    return extractor.extract_from_image(image_path)

def extract_text_from_pdf(pdf_path: str, backend: str = "tesseract", **kwargs) -> str:
    extractor = OCRExtractor(backend=backend, **kwargs)
    return extractor.extract_from_pdf(pdf_path)

def load_document(file_path: str, backend: str = "tesseract", **kwargs) -> str:
    extractor = OCRExtractor(backend=backend, **kwargs)
    return extractor.extract_from_file(file_path)

# Exemplo de uso
if __name__ == "__main__":
    # Teste com Tesseract
    text = load_document("documento.pdf", backend="tesseract", psm=6)
    print("Tesseract:\n", text[:500])

    # Teste com EasyOCR (se instalado)
    if EASYOCR_AVAILABLE:
        text_easy = load_document("documento.pdf", backend="easyocr")
        print("EasyOCR:\n", text_easy[:500])

    # Teste com PaddleOCR (se instalado)
    if PADDLEOCR_AVAILABLE:
        text_paddle = load_document("documento.pdf", backend="paddleocr")
        print("PaddleOCR:\n", text_paddle[:500])