# server.py (Lógica auxiliar)

import os
import uuid
import json
import re
# CORREÇÃO AQUI: Importar Tuple, List, Dict, Any, Optional
from typing import List, Dict, Any, Optional, Tuple
import pandas as pd
import math
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
from io import BytesIO
# Importe suas classes de cálculo
from Estrutura import TEstrutura, TOption, TOptionStatus, TCategoria
from Oplab import TOpLab, TOplabStockData

# ====================================================================
# CONFIGURAÇÕES GLOBAIS
# ====================================================================

# FORÇAR CAMINHO ABSOLUTO:
# Pega o caminho da pasta onde o server.py está localizado
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ESTRUTURAS_DIR = os.path.join(BASE_DIR, 'estruturas')

if not os.path.exists(ESTRUTURAS_DIR):
    os.makedirs(ESTRUTURAS_DIR)

ESTRUTURAS_DIR = 'estruturas'
if not os.path.exists(ESTRUTURAS_DIR):
    os.makedirs(ESTRUTURAS_DIR)

ESTRUTURAS_CARREGADAS: Dict[str, TEstrutura] = {} # Armazena estruturas carregadas na memória


CDI_SIMULACAO = 0.01


# ====================================================================
# NOVAS FUNÇÕES DE LOTE E COTAÇÃO
# ====================================================================


def F(valor, colorir=False, percentual=False):
    """
    Formatação Definitiva:
    - Erro/NaN: ---
    - Int: 400
    - Float: 60.00
    - Texto (C -> Verde, V -> Vermelho)
    - Números (Positivo -> Verde, Negativo -> Vermelho)
    """
    # 1. Tratamento de Erro (NaN/None)
    if valor is None or (isinstance(valor, float) and not math.isfinite(valor)):
        return '<span>---</span>'

    # --- NOVO: SUPORTE A ENUMS ---
    # Se o valor for um membro de Enum (como TCategoria ou TOptionStatus),
    # extraímos apenas o .value dele.
    if hasattr(valor, 'value'):
        valor = valor.value
    # -----------------------------

    # 2. Formatação do texto baseada no tipo original
    if isinstance(valor, int):
        texto = f"{valor:,}"
    elif isinstance(valor, float):
        texto = f"{valor:,.2f}"
    else:
        texto = str(valor)

    # 3. Sufixo de percentual (apenas se for número)
    if percentual and not isinstance(valor, str):
        texto += "%"

    # 4. Lógica de Cores
    if colorir:
        # TESTE PARA TEXTO (C ou V)
        if isinstance(valor, str):
            v_limpo = valor.strip().upper()
            if v_limpo in ['C', 'COMPRA']:
                return f'<span class="text-profit">{texto}</span>'
            elif v_limpo in ['V', 'VENDA']:
                return f'<span class="text-loss">{texto}</span>'

        # TESTE PARA NÚMEROS (Lucro ou Prejuízo)
        elif isinstance(valor, (int, float)):
            if valor > 0.0001: # Evita erro de arredondamento
                return f'<span class="text-profit">{texto}</span>'
            elif valor < -0.0001:
                return f'<span class="text-loss">{texto}</span>'

    # 5. Retorno neutro (se colorir=False ou se for um texto que não C/V)
    return f'<span>{texto}</span>'



def F_stack(top, bottom, colorir=False):
    """Empilha dois valores. Se forem números, chama a F() automaticamente."""
    t = F(top, colorir=colorir) if isinstance(top, (int, float)) else top
    b = F(bottom, colorir=colorir) if isinstance(bottom, (int, float)) else bottom
    return f'<div class="val-unit">{t}</div><div class="val-total">{b}</div>'



def F_inline(unit, total, colorir=False, afixo1=["", ""], afixo2=["", ""]):
    """
    Exibe os valores lado a lado: Prefixo1 Unit Sufixo1 (Prefixo2 Total Sufixo2)
    afixo = [prefixo, sufixo]
    """
    # Formata os valores básicos usando a função F original
    u = F(unit, colorir=colorir) if isinstance(unit, (int, float)) else unit
    t = F(total, colorir=colorir) if isinstance(total, (int, float)) else total

    # Monta as strings com os afixos
    # afixo[0] é o que vem antes, afixo[1] é o que vem depois
    res_unit = f"{afixo1[0]}{u}{afixo1[1]}"
    res_total = f"{afixo2[0]}{t}{afixo2[1]}"

    return f'<span class="val-unit">{res_unit}</span> <span class="text-muted small">{res_total}</span>'



def sincronizar_propriedades_estrutura(estrutura: TEstrutura) -> TEstrutura:
    """
    Única função de cálculo. Regras:
    1. ValorIn da estrutura é SEMPRE UNITÁRIO (preço médio de entrada).
    2. ValorNow da estrutura é SEMPRE TOTAL (valor de mercado agora).
    3. Se qualquer perna tiver preço <= 0 ou None, ValorNow vira NaN.
    """
    #print('\n\nsincronizar_propriedades_estrutura:', estrutura.txt(),'\n\n')
    if not estrutura:
        return estrutura

    qb = abs(estrutura.QuantBase) if estrutura.QuantBase != 0 else 1

    estrutura.CalcValorIn()

    # 1. Custo de Entrada TOTAL (para cálculo de lucro)
    custo_entrada_total = estrutura.ValorIn #* qb

    # 2. Valor de Mercado TOTAL (v_now_raw)
    v_now_total = 0.0
    for option in estrutura.Options:
        if option.Status in [TOptionStatus.ZERADA, TOptionStatus.EXPIRADA]:
            # Expirada: vale 0, não é erro
            v_now_total += option.Quant * 0.0
        else:
            p_now = option.ValorNow
            if p_now is None or p_now <= 0:
                v_now_total = float('nan')
                break
            v_now_total += option.Quant * p_now

    # 3. Atualiza os campos da estrutura (Sempre em valores TOTAIS)
    estrutura.ValorNow = v_now_total
    # Lucro Total = Mercado Total - Entrada Total
    estrutura.ValorLucro = v_now_total - custo_entrada_total

    # 4. Atualiza Lucro Alvo (Sempre TOTAL)
    # ValorAim na importação é unitário, então multiplicamos por QB
    v_alvo_total = estrutura.ValorAim
    estrutura.LucroAim = v_alvo_total - custo_entrada_total

    return estrutura


def carregar_estruturas_em_lote() -> Dict[str, TEstrutura]:
    """
    Carrega todas as estruturas salvas do disco para a memória (variável global).
    Retorna o dicionário de estruturas.
    """
    global ESTRUTURAS_CARREGADAS

    # 1. Limpa o cache existente
    ESTRUTURAS_CARREGADAS.clear()

    # 2. Obtém a lista de arquivos
    estrategias_resumo = carregar_estrategias_salvas_lista()

    # 3. Carrega cada estrutura detalhada
    for resumo in estrategias_resumo:
        estrutura = carregar_estrutura_detalhada(resumo['id'])
        if estrutura:
            ESTRUTURAS_CARREGADAS[estrutura.id] = estrutura

    return ESTRUTURAS_CARREGADAS


def cotar_e_atualizar_estruturas_em_lote() -> None:
    """
    Cria uma lista única de todas as opções de todas as estruturas carregadas,
    cota os preços de mercado (Bid/Ask) e atualiza ValorNow e ValorOut.
    """
    global ESTRUTURAS_CARREGADAS

    if not ESTRUTURAS_CARREGADAS:
        print("Aviso: Nenhuma estrutura carregada para cotação.")
        return

    # 1. Agrupar Símbolos Únicos (Opções e Ativos Base)
    simbolos_unicos = set()
    opcoes_referencia: Dict[str, List[TOption]] = {} # {symbol: [op1, op2, ...]}

    for est_id, estrutura in ESTRUTURAS_CARREGADAS.items():
        if estrutura.AtivoBase:
            simbolos_unicos.add(estrutura.AtivoBase)

        for option in estrutura.Options:
            simbolos_unicos.add(option.Nome)
            if option.Nome not in opcoes_referencia:
                opcoes_referencia[option.Nome] = []
            opcoes_referencia[option.Nome].append(option)

    if not simbolos_unicos:
        return

    # 2. Cotação na API (TOpLab)
    oplab_instance = TOpLab().Create()
    try:
        comma_separated_symbols = ",".join(simbolos_unicos)
        print(f"DEBUG: Consultando API em lote para {len(simbolos_unicos)} símbolos.")
        stock_data: List[TOplabStockData] = oplab_instance.CotarSymbolsplus(comma_separated_symbols)
    finally:
        oplab_instance.Destroy()

    # Mapeamento rápido de cotações: {Symbol: TOplabStockData}
    cotacoes_map = {data.Symbol: data for data in stock_data}
    #print('\n\n\cotar_e_atualizar_estruturas_em_lote: CotacaoMap,',cotacoes_map,'\n\n')
    #print(f"DEBUG: {cotacoes_map['AXIAE640'].Symbol} - Bid: {cotacoes_map['AXIAE640'].Bid}, Ask: {cotacoes_map['AXIAE640'].Ask}, Due: {cotacoes_map['AXIAE640'].DueDate}")


    # 3. Atualizar cada Opção em todas as Estruturas
    for est_id, estrutura in ESTRUTURAS_CARREGADAS.items():
        #print('\n\n','cotar_e_atualizar_estruturas_em_lote:', estrutura.txt(),'\n\n')
        custo_montagem_agora = 0.0
        #valor_out_total_pernas = 0.0 # NOVO: Variável para acumular ValorOut das pernas

        # Lógica de atualização de ValorAtivoNow no topo do loop
        cotacao_ativo = cotacoes_map.get(estrutura.AtivoBase)
        if cotacao_ativo:
            # NOVO: Atualiza ValorAtivoNow e ValorAtivo
            estrutura.ValorAtivoNow = cotacao_ativo.ValorAtivo or cotacao_ativo.Ask
            #estrutura.ValorAtivo = estrutura.ValorAtivo

        for option in estrutura.Options:
            cotacao = cotacoes_map.get(option.Nome)
            #print('\n\ncotacao:', cotacao )
            #print('\n\ncotacao:', cotacao.DueDate )

            if cotacao:
                # Regra de atualização do Status baseada no Book
                if cotacao.Bid <= 0 and cotacao.Ask <= 0:
                    option.Status = TOptionStatus.DISP_NA
                else:
                    option.Status = TOptionStatus.ATIVA
                # Atualiza os valores de mercado
                #if option.Quant < 0: # vendi -> atualiza valor de compra
                #    option.ValorIn = cotacao.Ask
                #else: # comprei -> atualiza valor de venda
                #    option.ValorIn = cotacao.Bid

                # Regra de atualização do ValorNow e ValorOut (conforme solicitado):
                if option.Quant < 0: # Venda Original (Desmontar = Comprar de volta @ Ask)
                    option.ValorNow = cotacao.Ask   # Custo de mercado (para P&L)
                    #option.ValorOut = cotacao.Ask   # Valor de desmontagem
                else: # Compra Original (Desmontar = Vender @ Bid)
                    option.ValorNow = cotacao.Bid   # Preço de mercado (para P&L)
                    #option.ValorOut = cotacao.Bid   # Valor de desmontagem

                # Acumula o custo de montagem AGORA (usado para atualizar ValorIn da Estrutura)
                # NOTA: O ValorIn da opção DEVE ser o Valor da Transação Original (restaurado no fluxo de importação)
                if option.Quant > 0:
                    custo_montagem_agora += option.Quant * option.ValorAsk
                else:
                    custo_montagem_agora += option.Quant * option.ValorBid

                option.strike = cotacao.Strike
                option.Vencimento = cotacao.Vencimento

                # D. CORREÇÃO DA DATA: Se o vencimento já passou, status é EXPIRADA
                # Se option.DueDate for '2024-05-10' e hoje for '2024-05-15', ela expirou.
                # Verificamos hasattr para não quebrar se o campo estiver vazio
                due_date_str = getattr(cotacao, 'DueDate', None) or getattr(cotacao, 'DataVencimento', None)
                #print('\n\ndue_date_str',due_date_str)

                if due_date_str:  # Verifica se não é None e nem string vazia

                    # Garante que estamos comparando apenas os 10 primeiros caracteres (AAAA-MM-DD)
                    # caso a API retorne data com hora (timestamp)
                    vencimento_limpo = str(due_date_str)[:10]

                    datadodia = datetime.now().date().isoformat()
                    if vencimento_limpo < datadodia:
                        if option.Tipo in ('call', 'put'):
                            option.Status = TOptionStatus.EXPIRADA
                            option.ValorNow = 0.0

            else:
                # Não retornou da API
                if option.Tipo in ('call', 'put'):
                    option.Status = TOptionStatus.EXPIRADA
                    option.ValorNow = 0.0  # expirada sem valor

            # Atualiza ValorAtivo e Sigma da Estrutura se forem encontrados (Fallback)
            if cotacoes_map.get(estrutura.AtivoBase) and estrutura.ValorAtivoNow == 0:
                 data_ativo = cotacoes_map.get(estrutura.AtivoBase)
                 estrutura.ValorAtivoNow = data_ativo.ValorAtivo or data_ativo.Ask
                 estrutura.Sigma = data_ativo.Sigma or estrutura.Sigma
        estrutura.MenorVencimento = estrutura.GetMenorVencimento()

        #print('\n\ncotar_e_atualizar_estruturas_em_lote: ', estrutura.txt())

        # 4. Calcule o ValorNow da Estrutura (Custo de Montagem AGORA)
        # O ValorNow da ESTRUTURA é o custo total se fosse montar/desmontar AGORA.
        # Mas aqui usaremos o campo structure.ValorOut para esse propósito para evitar confusão.
        #estrutura.ValorNow = estrutura.CalcPayoffAntecipado(estrutura.ValorAtivo, 0, 0.01)
        # Se você quiser que ValorNow seja o Custo de Montagem/Desmontagem AGORA:
        # estrutura.ValorNow = custo_montagem_agora # Isso requer uma lógica de sinais mais apurada


# ====================================================================
# FUNÇÕES DE PERSISTÊNCIA E AUXILIARES
# ====================================================================

def carregar_estrategias_salvas_lista() -> List[Dict[str, Any]]:
    """Carrega dados de todas as estruturas que NÃO começam com '_'."""
    estrategias = []
    if not os.path.exists(ESTRUTURAS_DIR):
        return estrategias

    for filename in os.listdir(ESTRUTURAS_DIR):
        # FILTRO: Apenas arquivos .json que não começam com underscore
        if filename.endswith(".json") and not filename.startswith("_"):
            filepath = os.path.join(ESTRUTURAS_DIR, filename)
            try:
                with open(filepath, 'r') as f:
                    print('\nfilepath',filepath,'\n')
                    data = json.load(f)
                    estrategias.append({
                        "id": data.get("id"),
                        "nome": data.get("nome"),
                        "ativo": data.get("ativoBase"),
                        "caminho_arquivo": filepath,
                        "tipo": "Salva"
                    })
            except Exception as e:
                print(f"Erro ao carregar {filename}: {e}")
    #print('\n\nestrategias: ',estrategias,'\n\n')
    return estrategias

def carregar_estrutura_detalhada(estrategia_id: str) -> Optional[TEstrutura]:
    """Carrega uma TEstrutura completa de um arquivo JSON pelo ID."""
    for filename in os.listdir(ESTRUTURAS_DIR):
        if filename.endswith(".json") and not filename.startswith("_"):
            filepath = os.path.join(ESTRUTURAS_DIR, filename)
            try:
                with open(filepath, 'r') as f:
                    data = json.load(f)
                    if data.get("id") == estrategia_id:
                        estrutura = TEstrutura()
                        estrutura.id = data.get("id")
                        estrutura.QuantBase = data.get("quantBase",1) or 1
                        estrutura.AtivoBase = data.get("ativoBase")
                        estrutura.ValorAtivoIn = data.get("valorAtivoIn", 0.1) or 0.1
                        estrutura.Sigma = data.get("sigma")
                        estrutura.Nome = data.get("nome")
                        estrutura.ValorIn = data.get("valorIn")
                        estrutura.ValorAim = data.get("valorAim", 0.0)
                        estrutura.ValorOut = data.get("valorOut", 0.0)
                        estrutura.DTE = data.get("DTE") or data.get("dte") or 0
                        estrutura.Info = data.get("Info") or data.get("info") or ""
                        try:
                            estrutura.Categoria = TCategoria(data.get("categoria", ""))
                        except ValueError:
                            estrutura.Categoria = TCategoria.SEM_CATEGORIA
                        estrutura.Corretora = data.get("corretora", "")

                        for op_data in data.get("opcoes", []):
                            op = TOption()
                            op.Quant = op_data.get("quant")
                            op.Tipo = op_data.get("tipo")
                            op.Nome = op_data.get("nome")
                            op.Oper = op_data.get("oper", "C")
                            op.strike = op_data.get("strike")
                            op.Vencimento = op_data.get("vencimento_dias")
                            op.ValorIn = op_data.get("valorIn")
                            op.TransacaoValue = op_data.get("transacaoValue", 0.0)
                            op.ValorBid = op_data.get("valorBid")
                            op.ValorAsk = op_data.get("valorAsk")
                            op.ValorLast = op_data.get("valorLast")
                            op.expirada = op_data.get("expirada", False)
                            estrutura.Options.Add(op)

                        #print('\n\nout: carregar_estrutura_detalhada: data',data)
                        #print('out: carregar_estrutura_detalhada: estrutura',json.dumps(estrutura.__dict__, indent=4, default=str))
                        return estrutura
            except Exception as e:
                print(f"Erro ao carregar detalhe de {filename}: {e}")
    return None

def salvar_estrutura(estrutura: TEstrutura, nome_estrategia: str) -> str:
    """Salva a estrutura e limpa QUALQUER arquivo antigo com o mesmo ID."""
    global ESTRUTURAS_CARREGADAS

    # GARANTIA DE ID:
    # Se não tem o atributo id ou se o id está vazio, gera um novo
    if not hasattr(estrutura, 'id') or not estrutura.id:
        estrutura.id = str(uuid.uuid4())

    # 1. VARREDURA DE LIMPEZA (BACKUP)
    # Procuramos por qualquer arquivo que tenha o mesmo ID lá dentro,
    # não importa como o arquivo se chama fisicamente.
    for filename in os.listdir(ESTRUTURAS_DIR):
        # Apenas arquivos ativos (que não começam com _)
        if filename.endswith(".json") and not filename.startswith("_"):
            filepath = os.path.join(ESTRUTURAS_DIR, filename)
            try:
                with open(filepath, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                    # Se o ID interno bater, é o mesmo objeto (mesmo que o nome do arquivo seja diferente)
                    if str(data.get("id")) == str(estrutura.id):
                        f.close() # Fecha para poder renomear no Windows/Google Drive

                        backup_name = f"_{os.path.splitext(filename)[0]}_bak.json"
                        backup_path = os.path.join(ESTRUTURAS_DIR, backup_name)

                        if os.path.exists(backup_path):
                            os.remove(backup_path)
                        os.rename(filepath, backup_path)
                        print(f"DEBUG: Arquivo antigo {filename} movido para backup.")
            except Exception as e:
                continue

    # 2. SALVAMENTO DO NOVO ARQUIVO (PADRONIZADO)
    # Usamos o nome da estratégia e o ID completo para o nome do arquivo
    nome_seguro = re.sub(r'[^a-zA-Z0-9_\-]', '_', nome_estrategia)
    novo_filename = f"{nome_seguro}_{estrutura.id}.json"
    novo_filepath = os.path.join(ESTRUTURAS_DIR, novo_filename)

    # Atualiza dados da estrutura
    estrutura.Nome = nome_estrategia

    # Prepara o dicionário para salvar (usando o método da classe)
    dados_para_salvar = TEstrutura.ConverterEstruturaParaEstruturaJSON(estrutura)

    # Adicionamos campos extras que podem não estar no método padrão
    dados_para_salvar["valorAtivoNow"] = estrutura.ValorAtivoNow
    dados_para_salvar["valorOut"] = estrutura.ValorOut
    dados_para_salvar["valorNow"] = estrutura.ValorNow
    #dados_para_salvar["info"] = estrutura.Info

    with open(novo_filepath, 'w', encoding='utf-8') as f:
        json.dump(dados_para_salvar, f, indent=4)

    # 3. ATUALIZA O CACHE DE MEMÓRIA
    ESTRUTURAS_CARREGADAS[estrutura.id] = estrutura
    return estrutura.id



def obter_detalhes_estrutura_df(estrutura: Optional[TEstrutura]) -> pd.DataFrame:
    """Converte a TEstrutura para um DataFrame de exibição para a tabela de detalhes."""
    if not estrutura:
        return pd.DataFrame({'Info': ['Nenhuma estrutura selecionada ou carregada.']})

    data = []
    for op in estrutura.Options:
        valor_now = op.ValorLast if op.ValorLast > 0 else op.ValorAsk

        data.append({
            'Quant': op.Quant, 'Op': 'C' if op.Quant > 0 else 'V', 'Tipo': op.Tipo,
            'Nome': op.Nome, 'Strike': op.strike, 'Venc': op.Vencimento, 'Vol': estrutura.Sigma,
            'ValorIn': op.ValorIn, 'ValorNow': valor_now, 'ValorOut': op.ValorOut,
        })
    df = pd.DataFrame(data)

    # Formatação
    df['Strike'] = df['Strike'].map('{:,.2f}'.format)
    df['Vol'] = df['Vol'].map('{:,.2f}'.format)
    df['ValorIn'] = df['ValorIn'].map('{:,.2f}'.format)
    df['ValorNow'] = df['ValorNow'].apply(lambda x: '{:,.2f}'.format(x) if x > 0 else '')
    df['ValorOut'] = df['ValorOut'].apply(lambda x: '{:,.2f}'.format(x) if x > 0 else '')

    colunas_finais = [
        'Quant', 'Op', 'Tipo', 'Nome', 'Strike',
        'Venc', 'Vol', 'ValorIn', 'ValorNow', 'ValorOut'
    ]

    return df[colunas_finais]

##def calcular_payoff_mock_df(estrategia_id: str, nome: str, min_p: float, max_p: float) -> pd.DataFrame:
##    """Fallback para gerar um DataFrame simples (mock) quando a estrutura real não está disponível."""
##    precos = np.linspace(min_p, max_p, 10)
##    payoffs = np.zeros_like(precos)
##
##    if 'Long' in nome or 'Call' in nome:
##         payoffs = (precos - (min_p + max_p) / 2) * 0.05
##
##    return pd.DataFrame({
##        'Preço do Ativo': precos,
##        'Lucro/Prejuízo': payoffs,
##        'Estratégia': nome
##    })

##def gerar_svg_payoff_mock(estrategia_info: Dict[str, str], min_p: float, max_p: float) -> str:
##    """Gera o SVG para o gráfico mock usando Matplotlib."""
##    df_payoff_mock = calcular_payoff_mock_df(
##        estrategia_info['id'],
##        estrategia_info['nome'],
##        min_p, max_p
##    )
##
##    fig_mock, ax_mock = plt.subplots(figsize=(10, 6))
##    ax_mock.plot(df_payoff_mock['Preço do Ativo'], df_payoff_mock['Lucro/Prejuízo'], color='blue')
##    ax_mock.axhline(0, color='gray', linestyle='--')
##    ax_mock.set_title(f"Payoff Mock: {estrategia_info['nome']}")
##    ax_mock.set_xlabel('Preço do Ativo')
##    ax_mock.set_ylabel('Lucro/Prejuízo (P&L)')
##    ax_mock.grid(True)
##
##    buf = BytesIO()
##    plt.savefig(buf, format='svg')
##    plt.close(fig_mock)
##    return buf.getvalue().decode('utf-8')

##def gerar_svg_payoff_real(estrutura: TEstrutura) -> str:
##    """
##    Função wrapper para gerar o SVG de uma estrutura real.
##    """
##    print('gerar_svg_payoff_real: estrutura', estrutura.__dict__)
##    # 1. Cria a cópia unitária (proporcional a 1 ação)
##    estrutura_para_plot = estrutura.GetUnitaryCopy()
##    estrutura_para_plot.ValorAim *= estrutura_para_plot.QuantBase
##    estrutura_para_plot.ValorIn *= estrutura_para_plot.QuantBase
##    estrutura_para_plot.ValorNow *= estrutura_para_plot.QuantBase
##
##
##    # 2. Calcula os limites automáticos do gráfico
##    min_x, max_x = estrutura_para_plot.CalcGraphLimits(0.0, 0.0, estrutura_para_plot.GetMenorVencimento())
##
##    # 3. CHAMADA CORRIGIDA: use o nome 'estrutura_para_plot'
##    return estrutura_para_plot.PlotarPayoffNoVencimento_Matplotlib_SVG(
##        min_x,
##        max_x,
##        CDI_SIMULACAO,
##        plotarStrikes=True,
##        plotarValorAtivo=True,
##        plotarSigmas=True
##        # Se você adicionou o parâmetro exibir_curva_now=True no Estrutura.py,
##        # ele já funcionará aqui por padrão.
##    )

def gerar_svg_payoff_real(estrutura: TEstrutura) -> str:
    """
    Função wrapper para gerar o SVG de uma estrutura real.
    """
    #print('gerar_svg_payoff_real: estrutura', estrutura.txt())
    # 1. Cria a cópia unitária (proporcional a 1 ação)
    estrutura_para_plot = estrutura.GetUnitaryCopy()
    estrutura_para_plot.ValorAim *= estrutura_para_plot.QuantBase
    estrutura_para_plot.ValorIn *= estrutura_para_plot.QuantBase
    estrutura_para_plot.ValorNow *= estrutura_para_plot.QuantBase

    # 1. Calcula limites
    min_x, max_x = estrutura_para_plot.CalcGraphLimits(0.0, 0.0, estrutura.GetMenorVencimento())

    # 2. Chama o NOVO método que você acabou de colocar no Estrutura.py
    #print('\n chamando PlotarPayoff_Plotly')
    return estrutura_para_plot.PlotarPayoff_Plotly(min_x, max_x, CDI_SIMULACAO)




def excluir_estrategia_por_id(estrategia_id: str) -> Tuple[bool, str]:
    global ESTRUTURAS_CARREGADAS

    print(f"DEBUG: Iniciando exclusão do ID: {estrategia_id}")

    # 1. Lista os arquivos no diretório
    try:
        arquivos = os.listdir(ESTRUTURAS_DIR)
    except Exception as e:
        return False, f"Erro ao acessar diretório: {e}"

    for filename in arquivos:
        # Só processa arquivos .json que não são backups/deletados
        if filename.endswith(".json") and not filename.startswith("_"):
            filepath = os.path.join(ESTRUTURAS_DIR, filename)

            try:
                # Encoding utf-8 é vital para nomes com acento
                with open(filepath, 'r', encoding='utf-8') as f:
                    data = json.load(f)

                    # Verifica se o ID bate
                    if str(data.get("id")) == str(estrategia_id):
                        nome_est = data.get("nome", "Sem Nome")

                        # Prepara o novo nome (Soft Delete)
                        novo_filename = f"_{filename}"
                        novo_caminho = os.path.join(ESTRUTURAS_DIR, novo_filename)

                        # Se já existir um arquivo com o nome de destino, removemos
                        if os.path.exists(novo_caminho):
                            os.remove(novo_caminho)

                        # Fecha o arquivo antes de renomear (no Windows isso é crítico)
                        f.close()

                        os.rename(filepath, novo_caminho)

                        # Limpa do cache de memória
                        if estrategia_id in ESTRUTURAS_CARREGADAS:
                            del ESTRUTURAS_CARREGADAS[estrategia_id]

                        print(f"DEBUG: Estratégia {nome_est} ocultada com sucesso.")
                        return True, nome_est

            except Exception as e:
                # Se der erro no 'with open' ou 'json.load', ele mostra aqui no console
                print(f"ERRO ao ler arquivo {filename}: {e}")
                continue

    print("DEBUG: Fim do loop, ID não encontrado.")
    return False, "Estratégia não localizada nos arquivos ativos."






# ====================================================================
# FUNÇÕES DE GERAÇÃO DE DATAFRAME (REFATORADAS E CORRIGIDAS)
# ====================================================================


def obter_resumo_estrutura_organizado(estrutura: Optional[TEstrutura]):
    if not estrutura:
        return {"nome": "Nenhuma Estratégia Selecionada", "grupos": []}

    qb = abs(estrutura.QuantBase) if estrutura.QuantBase != 0 else 1
    dias_mercado = estrutura.GetMenorVencimento()
    dias_manual = estrutura.DTE

    # Lucros Unitários (Para o Top do stack)
    lucro_alvo = (estrutura.ValorAim - estrutura.ValorIn)

    # Cálculos de Normalização
    lucro_normalizado = lucro_alvo / estrutura.ValorLucro if estrutura.ValorLucro else float('nan')
    dias_normalizado = dias_mercado / dias_manual if dias_manual else float('nan')

    dados = [
        {"cat": "1. MONTAGEM", "itens": [
            ("Nome da Estratégia", "Nome", estrutura.Nome),
            ("Categoria", "Categoria", F(estrutura.Categoria) or "-"),
            ("Corretora", "Corretora", estrutura.Corretora or "-"),
            ("Quantidade Base", "QuantBase", F(estrutura.QuantBase)), # Exibe como INT (ex: 400)
            ("Dias p/ o Vencimento", "DTEs", F_inline(dias_mercado, dias_manual, afixo2=["[","]"])),
            ("Ativo Base", "AtivoBase", estrutura.AtivoBase),
            ("Custo na Entrada", "ValorIn", F_inline(estrutura.ValorIn / qb, estrutura.ValorIn, afixo2=["Tot: ",""])),
            ("ID Único", "id", estrutura.id),
        ]},
        {"cat": "2. ACOMPANHAMENTO", "itens": [
            ("Informações", "Info", estrutura.Info or "-"),
            # Mark-to-Market: Preço Unitário (Now/QB) e Preço Total (Now)
            ("Valor Atual", "ValorNow", F_inline(estrutura.ValorNow / qb, estrutura.ValorNow, afixo2=["Tot: ",""])),
            ("P&L Atual", "ValorLucro", F_inline(estrutura.ValorLucro / qb, estrutura.ValorLucro, colorir=True, afixo2=["Tot: ",""])),
            ("Preço Spot Atual", "ValorAtivoNow", F(estrutura.ValorAtivoNow)),
        ]},
        {"cat": "3. DESMONTAGEM", "itens": [
            ("Alvo p/ a Saída", "ValorAim", F_inline(estrutura.ValorAim / qb, estrutura.ValorAim, afixo2=["Tot: ",""])),
            ("Lucro Alvo", "LucroAim", F_inline(lucro_alvo / qb, lucro_alvo, colorir=True, afixo2=["Tot: ",""])),
            ("Lucro Normalizado", "Normalizado",
                F_inline(lucro_normalizado, f"{F(lucro_alvo)} / {F(estrutura.ValorLucro)}", colorir=True, afixo2=["Tot: ",""])),
            ("Dias Normalizado", "Normalizado",
                F_inline(dias_normalizado, f"{F(dias_mercado)} / {F(dias_manual)}", afixo2=["[","]"])),
        ]}
    ]
    return {"nome": estrutura.Nome, "grupos": dados}




def obter_opcoes_estrutura(estrutura: Optional[TEstrutura]) -> List[Dict[str, Any]]:
    if not estrutura or not estrutura.Options:
        return []

    data = []
    for op in estrutura.Options:
        # Criamos o dicionário usando TIPOS NATIVOS do Python
        # Isso garante que Int continue Int e Float continue Float
        perna = {
            'Quant': F(int(op.Quant)),
            'Op': F(op.Oper, colorir=True),
            'Tipo': op.Tipo,
            'Nome': op.Nome,
            'Strike': F(float(op.strike) if op.Tipo != 'ativo' else math.nan),
            'Venc': F(int(op.Vencimento) if op.Tipo != 'ativo' else math.nan),
            'ValorIn': F(float(op.ValorIn)),
            'ValorNow': F(float(op.ValorNow) if op.ValorNow is not None else None),
            'Status': op.Status.value,
        }
        data.append(perna)

    return data # Retornamos a LISTA pura, sem passar pelo Pandas



def obter_estrutura_para_edicao(estrutura: TEstrutura) -> List[Dict[str, Any]]:
    """
    Prepara uma lista de dicionários com os campos editáveis de cada opção.
    """
    dados = []
    for op in estrutura.Options:
        dados.append({
            'Quant': op.Quant,
            'Oper': op.Oper,
            'Tipo': op.Tipo,
            'Nome': op.Nome,
            'strike': op.strike,
            'Vencimento': op.Vencimento,
            'ValorIn': op.ValorIn, # Preço de transação original
        })
    return dados

