from threading import Thread
from time import sleep
from db.crud import Produto, Orcamento, LancamentoReceita, Despesa, LancamentoDespesa
from db.database import SessionLocal
from sqlalchemy.orm import Session
from datetime import date, datetime
from dateutil.relativedelta import relativedelta
from db.models.DespesaVariavel import DespesaVariavel
from db.models.DespesaAvulsa import DespesaAvulsa
from db.models.LancamentoDespesa import VariavelValidado
from db.models.Orcamento import TipoOrcamento
from db.schemas.Schemas import OrcamentoBase, ProdutosOrcamento, ServicosOrcamento, LancamentoReceitaBase, LancamentoDespesaBase, TipoPeriodoLancamento


def buscar_lancamentos_de_orcamentos_que_precisam_ser_feitos(session: Session) -> list[LancamentoReceitaBase]:
    lancamentos_a_serem_feitos = []

    for orcamento in Orcamento.get_orcamentos(session):
        lancamentos_do_orcamento = LancamentoReceita.get_lancamentos_by_orcamento(orcamento.id, session)
        existe_primeiro_lancamento = False
        valor_total_orcamento = calcular_valor_total_do_orcamento(orcamento)

        for lancamento in lancamentos_do_orcamento:
            if lancamento.data == orcamento.data_emissao:
                existe_primeiro_lancamento = True

        if not existe_primeiro_lancamento:
            lancamentos_a_serem_feitos.append(LancamentoReceitaBase(
                id=None,
                orcamento_id=orcamento.id,
                data=orcamento.data_emissao.strftime("%Y-%m-%dT%H:%M:%S"),
                valor=valor_total_orcamento
            ))

        if orcamento.tipo != TipoOrcamento.Avulso:
            data_lancamento_orcamento = orcamento.data_emissao

            if orcamento.tipo == TipoOrcamento.Mensal:
                periodo_delta = relativedelta(months=1)
            elif orcamento.tipo == TipoOrcamento.Bimestral:
                periodo_delta = relativedelta(months=2)
            elif orcamento.tipo == TipoOrcamento.Trimestral:
                periodo_delta = relativedelta(months=3)
            elif orcamento.tipo == TipoOrcamento.Semestral:
                periodo_delta = relativedelta(months=6)
            elif orcamento.tipo == TipoOrcamento.Anual:
                periodo_delta = relativedelta(year=1)

            data_lancamento_orcamento += periodo_delta

            while data_lancamento_orcamento < (datetime.now() + relativedelta(days=30)):
                lancamento_existe = False

                for lancamento in lancamentos_do_orcamento:
                    if lancamento.data == data_lancamento_orcamento:
                        lancamento_existe = True

                if not lancamento_existe:
                    lancamentos_a_serem_feitos.append(LancamentoReceitaBase(
                        id=None,
                        orcamento_id=orcamento.id,
                        data=data_lancamento_orcamento.strftime("%Y-%m-%dT%H:%M:%S"),
                        valor=valor_total_orcamento
                    ))
                
                data_lancamento_orcamento += relativedelta(months=1)

    return lancamentos_a_serem_feitos

def buscar_lancamentos_de_despesas_que_precisam_ser_feitos(session: Session) -> list[LancamentoDespesaBase]:
    lancamentos_a_serem_feitos = []

    for despesa in Despesa.get_despesas(session):
        lancamentos_da_despesa = LancamentoDespesa.get_lancamentos_by_despesa(despesa.id, session)
        existe_primeiro_lancamento = False

        for lancamento in lancamentos_da_despesa:
            if lancamento.data_lancamento == despesa.data:
                existe_primeiro_lancamento = True

        if not existe_primeiro_lancamento:
            lancamentos_a_serem_feitos.append(LancamentoDespesaBase(
                id=None,
                despesa_id=despesa.id,
                data_lancamento=despesa.data.strftime("%Y-%m-%dT%H:%M:%S"),
                valor=despesa.valor,
                variavel_validado=VariavelValidado.NA
            ))

        if type(despesa) != DespesaAvulsa:
            data_lancamento_despesa = despesa.data
            periodo_lancamento_despesa = Despesa.get_periodo_lancamento_despesa(id_despesa=despesa.id, session=session)

            if periodo_lancamento_despesa.tipo == TipoPeriodoLancamento.DIARIO:
                periodo_delta = relativedelta(days=1)
            elif periodo_lancamento_despesa.tipo == TipoPeriodoLancamento.SEMANAL:
                periodo_delta = relativedelta(weeks=1)
            elif periodo_lancamento_despesa.tipo == TipoPeriodoLancamento.MENSAL:
                periodo_delta = relativedelta(months=1)
            elif periodo_lancamento_despesa.tipo == TipoPeriodoLancamento.BIMESTRAL:
                periodo_delta = relativedelta(months=2)
            elif periodo_lancamento_despesa.tipo == TipoPeriodoLancamento.TRIMESTRAL:
                periodo_delta = relativedelta(months=3)
            elif periodo_lancamento_despesa.tipo == TipoPeriodoLancamento.SEMESTRAL:
                periodo_delta = relativedelta(months=6)
            elif periodo_lancamento_despesa.tipo == TipoPeriodoLancamento.ANUAL:
                periodo_delta = relativedelta(years=1)
            else:
                periodo_delta = relativedelta(days=periodo_lancamento_despesa.valor_variavel)

            data_lancamento_despesa += periodo_delta
            while data_lancamento_despesa <= (datetime.now() + relativedelta(days=30)):
                lancamento_existe = False

                for lancamento in lancamentos_da_despesa:
                    if lancamento.data_lancamento == data_lancamento_despesa:
                        lancamento_existe = True

                if not lancamento_existe:
                    lancamentos_a_serem_feitos.append(LancamentoDespesaBase(
                        id=None,
                        despesa_id=despesa.id,
                        data_lancamento=data_lancamento_despesa.strftime("%Y-%m-%dT%H:%M:%S"),
                        valor=despesa.valor if type(despesa) != DespesaVariavel else 0.00,
                        variavel_validado=VariavelValidado.NA if type(despesa) != DespesaVariavel else VariavelValidado.Nao
                    ))
                
                data_lancamento_despesa += periodo_delta

    return lancamentos_a_serem_feitos

def calcular_valor_total_do_produto(produto: ProdutosOrcamento):
    return produto.preco_unitario * produto.quantidade

def calcular_valor_total_do_servico(servico: ServicosOrcamento):
    tempo_convertido = int(servico.tempo.hour) + (int(servico.tempo.minute) / 60)
    return servico.preco_por_hora * tempo_convertido

def calcular_valor_total_do_orcamento(orcamento: OrcamentoBase):
    valor_total = 0

    for produto in orcamento.produtos:
        valor_total += calcular_valor_total_do_produto(produto)
        
    for servico in orcamento.servicos:
        valor_total += calcular_valor_total_do_servico(servico)
    
    return valor_total

    
class ThreadLancadorReceitasDespesas(Thread):
    def __init__(self):
        Thread.__init__(self)

    def run(self):
        while True:
            session = SessionLocal()

            lancamentos_de_orcamentos = buscar_lancamentos_de_orcamentos_que_precisam_ser_feitos(session)

            for lancamento in lancamentos_de_orcamentos:
                LancamentoReceita.add_lancamento(lancamento, session)

            lancamentos_de_despesas = buscar_lancamentos_de_despesas_que_precisam_ser_feitos(session)

            for lancamento in lancamentos_de_despesas:
                LancamentoDespesa.add_lancamento(lancamento, session)

            sleep(60)
