import logging

from datetime import date
from typing import Optional, List
from sqlalchemy.ext.asyncio import AsyncSession
from models.omie_cliente import OmieCliente
from sqlalchemy import select, and_, or_, func, case
from models.omie_contas_receber import OmieContaReceber
from models.omie_contas_receber_boleto import OmieContaReceberBoleto

logger = logging.getLogger(__name__)

class OmieContaReceberCRUD:
    async def list(
        self,
        db: AsyncSession,
        *,
        cnpj: Optional[str] = None,
        data_inicio: Optional[date] = None,
        data_fim: Optional[date] = None,
        status: Optional[str] = None,
    ) -> List[OmieContaReceber]:
        """Lista contas a receber com filtros opcionais"""
        table_name = OmieContaReceber.__tablename__
        logger.info(f"OmieContaReceberCRUD.list chamado - Tabela: {table_name}")
        try:
            logger.debug("Construindo query SELECT...")
            stmt = select(OmieContaReceber)
            conditions = []
            
            if cnpj:
                logger.debug(f"Aplicando filtro por CNPJ: {cnpj}")
                cnpj_limpo = cnpj.replace('.', '').replace('/', '').replace('-', '').strip()
                conditions.append(OmieContaReceber.cnpj.like(f"%{cnpj_limpo}%"))
            
            if data_inicio:
                logger.debug(f"Aplicando filtro data_inicio: {data_inicio}")
                conditions.append(OmieContaReceber.data_vencimento >= data_inicio)
            
            if data_fim:
                logger.debug(f"Aplicando filtro data_fim: {data_fim}")
                conditions.append(OmieContaReceber.data_vencimento <= data_fim)
            
            if status:
                logger.debug(f"Aplicando filtro status: {status}")
                conditions.append(OmieContaReceber.status == status)
            
            if conditions:
                stmt = stmt.where(and_(*conditions))
            
            logger.info(f"Executando query na tabela {table_name}...")
            result = await db.execute(stmt)
            logger.debug("Query executada com sucesso")
            
            contas = list(result.scalars().all())
            logger.info(f"Query retornou {len(contas)} registros da tabela {table_name}")
            return contas
            
        except Exception as e:
            error_msg = str(e)
            logger.error(f"Erro em OmieContaReceberCRUD.list ao consultar {table_name}: {e}", exc_info=True)
            raise

class OmieContaReceberBoletoCRUD:
    async def list(
        self,
        db: AsyncSession,
        *,
        codigo_lancamento: Optional[str] = None,
        data_inicio: Optional[date] = None,
        data_fim: Optional[date] = None,
        status: Optional[str] = None,
    ) -> List[OmieContaReceberBoleto]:
        """Lista boletos com filtros opcionais"""
        table_name = OmieContaReceberBoleto.__tablename__
        logger.info(f"OmieContaReceberBoletoCRUD.list chamado - Tabela: {table_name}")
        try:
            logger.debug("Construindo query SELECT...")
            stmt = select(OmieContaReceberBoleto)
            conditions = []
            
            if codigo_lancamento:
                logger.debug(f"Aplicando filtro por codigo_lancamento: {codigo_lancamento}")
                conditions.append(OmieContaReceberBoleto.codigo_lancamento == codigo_lancamento)
            
            if data_inicio:
                logger.debug(f"Aplicando filtro data_inicio: {data_inicio}")
                conditions.append(OmieContaReceberBoleto.data_vencimento >= data_inicio)
            
            if data_fim:
                logger.debug(f"Aplicando filtro data_fim: {data_fim}")
                conditions.append(OmieContaReceberBoleto.data_vencimento <= data_fim)
            
            if status:
                logger.debug(f"Aplicando filtro status: {status}")
                conditions.append(OmieContaReceberBoleto.status == status)
            
            if conditions:
                stmt = stmt.where(and_(*conditions))
            
            logger.info(f"Executando query na tabela {table_name}...")
            result = await db.execute(stmt)
            logger.debug("Query executada com sucesso")
            
            boletos = list(result.scalars().all())
            logger.info(f"Query retornou {len(boletos)} registros da tabela {table_name}")
            return boletos
            
        except Exception as e:
            error_msg = str(e)
            logger.error(f"Erro em OmieContaReceberBoletoCRUD.list ao consultar {table_name}: {e}", exc_info=True)
            raise

class OmieBoletoCompletoCRUD:
    async def list_completo(
        self,
        db: AsyncSession,
        *,
        cnpj: Optional[str] = None,
        data_vencimento_inicio: Optional[date] = None,
        data_vencimento_fim: Optional[date] = None,
        data_emissao_boleto_inicio: Optional[date] = None,
        data_emissao_boleto_fim: Optional[date] = None,
        status_titulo: Optional[str] = None,
        pago: Optional[bool] = None,
    ) -> List:
        """Lista boletos com JOIN na tabela de contas a receber para obter CNPJ e data de vencimento"""
        logger.info("OmieBoletoCompletoCRUD.list_completo chamado - Fazendo JOIN entre boleto e conta a receber")
        try:
            valor_pago_calculado = case(
                (
                    OmieContaReceber.valor_pago.isnot(None),
                    OmieContaReceber.valor_pago
                ),
                (
                    func.upper(OmieContaReceber.status_titulo).in_([
                        'RECEBIDO', 'PAGO', 'BAIXADO', 'LIQUIDADO',
                        'RECEBIDO PARCIALMENTE', 'PAGO PARCIALMENTE'
                    ]),
                    OmieContaReceber.valor_documento
                ),
                else_=0
            ).label('valor_pago')
            
            data_pagamento_calculada = case(
                (
                    OmieContaReceber.data_pagamento.isnot(None),
                    OmieContaReceber.data_pagamento
                ),
                (
                    and_(
                        OmieContaReceber.data_pagamento.is_(None),
                        func.upper(OmieContaReceber.status_titulo).in_([
                            'RECEBIDO', 'PAGO', 'BAIXADO', 'LIQUIDADO'
                        ])
                    ),
                    func.coalesce(OmieContaReceber.data_baixa, OmieContaReceber.data_vencimento)
                ),
                else_=None
            ).label('data_pagamento')
            
            stmt = select(
                OmieContaReceberBoleto.codigo_lancamento_omie,
                OmieContaReceberBoleto.dDtEmBol.label('data_emissao_boleto'),
                OmieContaReceberBoleto.cNumBoleto.label('numero_boleto'),
                OmieContaReceberBoleto.cCodBarras.label('codigo_barras'),
                OmieContaReceberBoleto.cLinkBoleto.label('link_boleto'),
                OmieContaReceberBoleto.cCodStatus.label('status_boleto'),
                OmieContaReceberBoleto.cDesStatus.label('descricao_status'),
                OmieContaReceber.cnpj,
                OmieContaReceber.data_vencimento,
                OmieContaReceber.data_emissao,
                OmieContaReceber.valor_documento,
                OmieContaReceber.numero_documento,
                OmieContaReceber.status_titulo,
                OmieContaReceber.codigo_cliente_fornecedor,
                data_pagamento_calculada,
                valor_pago_calculado,
                OmieContaReceber.data_pagamento.label('data_pagamento_original'),
                OmieContaReceber.valor_pago.label('valor_pago_original'),
                OmieCliente.codigoClienteOmie.label('codigo_cliente_omie'),
                OmieCliente.cnpjCpf.label('cnpj_cliente'),
                OmieCliente.razaoSocial.label('razao_social_cliente'),
                OmieCliente.nomeFantasia.label('nome_fantasia_cliente'),
            ).join(
                OmieContaReceber,
                OmieContaReceberBoleto.codigo_lancamento_omie == OmieContaReceber.codigo_lancamento_omie
            ).outerjoin(
                OmieCliente,
                OmieContaReceber.codigo_cliente_fornecedor == OmieCliente.codigoClienteOmie
            )
            
            conditions = []
            
            if cnpj:
                logger.debug(f"Aplicando filtro por CNPJ: {cnpj}")
                cnpj_limpo = cnpj.replace('.', '').replace('/', '').replace('-', '').strip()
                logger.debug(f"CNPJ limpo do parâmetro: '{cnpj_limpo}'")
                cnpj_empresa_limpo = func.trim(
                    func.replace(
                        func.replace(
                            func.replace(OmieContaReceber.cnpj, '.', ''),
                            '/', ''
                        ),
                        '-', ''
                    )
                )
                cnpj_cliente_limpo = func.trim(
                    func.replace(
                        func.replace(
                            func.replace(OmieCliente.cnpjCpf, '.', ''),
                            '/', ''
                        ),
                        '-', ''
                    )
                )
                conditions.append(
                    or_(
                        cnpj_empresa_limpo.like(f"%{cnpj_limpo}%"),
                        cnpj_cliente_limpo.like(f"%{cnpj_limpo}%")
                    )
                )
            
            if data_vencimento_inicio:
                logger.debug(f"Aplicando filtro data_vencimento_inicio: {data_vencimento_inicio}")
                conditions.append(OmieContaReceber.data_vencimento >= data_vencimento_inicio)
            
            if data_vencimento_fim:
                logger.debug(f"Aplicando filtro data_vencimento_fim: {data_vencimento_fim}")
                conditions.append(OmieContaReceber.data_vencimento <= data_vencimento_fim)
            
            if data_emissao_boleto_inicio:
                logger.debug(f"Aplicando filtro data_emissao_boleto_inicio: {data_emissao_boleto_inicio}")
                conditions.append(OmieContaReceberBoleto.dDtEmBol >= data_emissao_boleto_inicio)
            
            if data_emissao_boleto_fim:
                logger.debug(f"Aplicando filtro data_emissao_boleto_fim: {data_emissao_boleto_fim}")
                conditions.append(OmieContaReceberBoleto.dDtEmBol <= data_emissao_boleto_fim)
            
            if status_titulo:
                logger.debug(f"Aplicando filtro status_titulo: {status_titulo}")
                conditions.append(OmieContaReceber.status_titulo == status_titulo)
            
            if pago is not None:
                logger.debug(f"Aplicando filtro pago: {pago}")
                if pago:
                    conditions.append(
                        or_(
                            OmieContaReceber.data_pagamento.isnot(None),
                            and_(
                                OmieContaReceber.valor_pago.isnot(None),
                                OmieContaReceber.valor_documento.isnot(None),
                                OmieContaReceber.valor_pago >= OmieContaReceber.valor_documento
                            ),
                            func.upper(OmieContaReceber.status_titulo).in_([
                                'RECEBIDO', 'PAGO', 'BAIXADO', 'LIQUIDADO', 
                                'RECEBIDO PARCIALMENTE', 'PAGO PARCIALMENTE'
                            ])
                        )
                    )
                else:
                    conditions.append(
                        and_(
                            OmieContaReceber.data_pagamento.is_(None),
                            or_(
                                OmieContaReceber.valor_pago.is_(None),
                                OmieContaReceber.valor_pago == 0,
                                and_(
                                    OmieContaReceber.valor_pago.isnot(None),
                                    OmieContaReceber.valor_documento.isnot(None),
                                    OmieContaReceber.valor_pago < OmieContaReceber.valor_documento
                                )
                            ),
                            or_(
                                OmieContaReceber.status_titulo.is_(None),
                                ~func.upper(OmieContaReceber.status_titulo).in_([
                                    'RECEBIDO', 'PAGO', 'BAIXADO', 'LIQUIDADO',
                                    'RECEBIDO PARCIALMENTE', 'PAGO PARCIALMENTE'
                                ])
                            )
                        )
                    )
            
            if conditions:
                stmt = stmt.where(and_(*conditions))
            
            logger.info("Executando query com JOIN...")
            result = await db.execute(stmt)
            logger.debug("Query executada com sucesso")
            
            boletos = list(result.all())
            logger.info(f"Query retornou {len(boletos)} registros")
            if cnpj:
                logger.info(f"Filtro por CNPJ '{cnpj}' (limpo: '{cnpj_limpo}') retornou {len(boletos)} registros")
            
            if boletos and len(boletos) > 0:
                primeiro = boletos[0]
                logger.debug(f"Primeiro registro - codigo_lancamento_omie: {getattr(primeiro, 'codigo_lancamento_omie', 'N/A')}, "
                           f"CNPJ empresa: {getattr(primeiro, 'cnpj', 'N/A')}, "
                           f"codigo_cliente_omie: {getattr(primeiro, 'codigo_cliente_omie', 'N/A')}, "
                           f"CNPJ cliente: {getattr(primeiro, 'cnpj_cliente', 'N/A')}, "
                           f"data_pagamento: {getattr(primeiro, 'data_pagamento', 'N/A')}, "
                           f"valor_pago: {getattr(primeiro, 'valor_pago', 'N/A')}")
            elif cnpj:
                logger.warning(f"Nenhum registro encontrado para CNPJ '{cnpj}' (limpo: '{cnpj_limpo}'). "
                             f"Verifique se o CNPJ existe na tabela omie_contaReceber ou omie_cliente.")
            
            return boletos
            
        except Exception as e:
            logger.error(f"Erro em OmieBoletoCompletoCRUD.list_completo: {e}", exc_info=True)
            raise

crud_contas_receber = OmieContaReceberCRUD()
crud_boletos = OmieContaReceberBoletoCRUD()
crud_boleto_completo = OmieBoletoCompletoCRUD()