#region Using declarations
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Xml.Serialization;
using NinjaTrader.Cbi;
using NinjaTrader.Gui;
using NinjaTrader.Gui.Chart;
using NinjaTrader.Gui.SuperDom;
using NinjaTrader.Gui.Tools;
using NinjaTrader.Data;
using NinjaTrader.NinjaScript;
using NinjaTrader.Core.FloatingPoint;
using NinjaTrader.NinjaScript.Indicators;
using NinjaTrader.NinjaScript.DrawingTools;

using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;
#endregion

// =====================================================================================
// TVBridge Socket Listener — Indicator (Multi-Instrument via Account.CreateOrder)
// =====================================================================================
// Este indicador recebe sinais do TVBridge Python via TCP e roteia ordens para
// QUALQUER instrumento usando Account.CreateOrder(), sem restrição de gráfico.
// =====================================================================================

namespace NinjaTrader.NinjaScript.Indicators
{
    public class TVBridgeSocketListener : Indicator
    {
        private TcpListener tcpListener;
        private Thread listenThread;
        private bool isRunning;

        #region Properties

        [NinjaScriptProperty]
        [Range(1024, 65535)]
        [Display(Name="Socket Port", Description="Porta TCP local (ex: 9091, 9092...)", Order=1, GroupName="1. Connection")]
        public int Port { get; set; }

        [NinjaScriptProperty]
        [Display(Name = "Trading Account", Description="Conta para enviar ordens", Order = 2, GroupName = "1. Connection")]
        [TypeConverter(typeof(AccountNameConverter))]
        public string TradingAccountName { get; set; }

        #endregion

        private object threadLock = new object();
        private List<string> pendingCommands = new List<string>();
        
        // Conta resolvida para envio de ordens
        private Account tradingAccount = null;
        
        // Memória do Basis Offset (Diferença entre TV Spot e NT8 Futures no Tick exato de Entrada)
        private double lastCalculatedBasisOffset = 0;

        private void LogBasis(string msg)
        {
            try 
            {
                Print(msg);
                string path = @"d:\Projetos\04_Trading\Copy\log_basis.txt";
                using (StreamWriter w = File.AppendText(path))
                {
                    w.WriteLine(DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss.fff") + " [NT8 C#] " + msg);
                }
            } 
            catch { }
        }

        // Cache Thread-Safe properties for Socket Status Ping
        private double cachedBalance = 0;
        private double cachedEquity = 0;
        private string cachedAccountName = "Desconhecida";
        private string cachedInstrumentName = "Desconhecido";
        private double cachedTickSize = 0.01;
        private double cachedPointValue = 1.0;
        private double cachedLastPrice = 0.0;

        protected override void OnStateChange()
        {
            if (State == State.SetDefaults)
            {
                Description                                 = @"Recebe sinais do TVBridge Python via TCP e roteia ordens para qualquer instrumento.";
                Name                                        = "TVBridgeSocketListener";
                Port                                        = 9091;
                TradingAccountName                          = "";
                Calculate                                   = Calculate.OnEachTick;
                IsOverlay                                   = true;
                DisplayInDataBox                            = false;
                DrawOnPricePanel                            = false;
                DrawHorizontalGridLines                     = false;
                DrawVerticalGridLines                       = false;
                PaintPriceMarkers                           = false;
                IsSuspendedWhileInactive                    = false;
            }
            else if (State == State.DataLoaded)
            {
                if (Instrument != null && Instrument.MasterInstrument != null)
                {
                    cachedInstrumentName = Instrument.MasterInstrument.Name;
                    cachedTickSize = Instrument.MasterInstrument.TickSize;
                    cachedPointValue = Instrument.MasterInstrument.PointValue;
                }
                
                // Resolve a conta de trading
                InitializeTradingAccount();
            }
            else if (State == State.Realtime)
            {
                // Start the TCP Listener on a separate thread
                isRunning = true;
                listenThread = new Thread(new ThreadStart(ListenForClients));
                listenThread.IsBackground = true;
                listenThread.Start();
                Print("TVBridgeSocketListener: Servidor iniciado na porta " + Port + " | Conta: " + (tradingAccount != null ? tradingAccount.Name : "NÃO ENCONTRADA"));
            }
            else if (State == State.Terminated)
            {
                isRunning = false;
                if (tcpListener != null)
                {
                    try {
                        tcpListener.Stop();
                    } catch { }
                }
                
                if (listenThread != null && listenThread.IsAlive)
                {
                    try {
                        listenThread.Join(1000);
                    } catch { }
                }
                Print("TVBridgeSocketListener: Servidor finalizado.");
            }
        }

        private void InitializeTradingAccount()
        {
            if (string.IsNullOrEmpty(TradingAccountName)) 
            {
                Print("⚠️ Nenhuma conta selecionada! Selecione uma conta no campo 'Trading Account'.");
                return;
            }
            
            lock (Account.All)
            {
                tradingAccount = Account.All.FirstOrDefault(a => a.Name == TradingAccountName);
            }
            
            if (tradingAccount != null)
            {
                cachedAccountName = tradingAccount.Name;
                Print("✅ Conta de trading conectada: " + tradingAccount.Name);
            }
            else
            {
                Print("❌ Conta '" + TradingAccountName + "' não encontrada!");
            }
        }

        protected override void OnBarUpdate()
        {
            UpdateThreadSafeCache();
            ProcessPendingCommands();
        }
        
        protected override void OnMarketData(MarketDataEventArgs marketDataUpdate)
        {
            UpdateThreadSafeCache();
            ProcessPendingCommands();
        }

        private void UpdateThreadSafeCache()
        {
            try
            {
                try
                {
                    if (CurrentBar >= 0)
                    {
                        cachedLastPrice = Close[0];
                    }
                } catch { }

                if (tradingAccount != null && tradingAccount.Connection != null && tradingAccount.Connection.Status == ConnectionStatus.Connected)
                {
                    cachedBalance = tradingAccount.Get(AccountItem.CashValue, Currency.UsDollar);
                    cachedEquity = cachedBalance + tradingAccount.Get(AccountItem.UnrealizedProfitLoss, Currency.UsDollar);
                    cachedAccountName = tradingAccount.Name;
                }
                else
                {
                    cachedAccountName = (tradingAccount != null) ? tradingAccount.Name : "Desconectado";
                }
            }
            catch { }
        }

        private void ProcessPendingCommands()
        {
            List<string> commandsToProcess = new List<string>();
            lock (threadLock)
            {
                if (pendingCommands.Count > 0)
                {
                    commandsToProcess.AddRange(pendingCommands);
                    pendingCommands.Clear();
                }
            }

            foreach (string commandStr in commandsToProcess)
            {
                ExecuteTradeCommand(commandStr);
            }
        }

        private void ExecuteTradeCommand(string commandStr)
        {
            try
            {
                Print("-> Processando Ordem: " + commandStr);
                string[] parts = commandStr.Split(',');
                
                if (parts.Length < 3)
                {
                    Print("Formato invalido. Esperado (ACAO,ATIVO,LOTE,[TV_PRICE],[SL],[TP]): " + commandStr);
                    return;
                }

                if (tradingAccount == null)
                {
                    Print("❌ ERRO: Nenhuma conta de trading configurada! Não é possível enviar ordens.");
                    return;
                }

                string action = parts[0].ToUpper().Trim();
                string targetSymbol = parts[1].Trim();
                double qtyDouble = 0;
                double tv_price = 0;
                double sl_price = 0;
                double tp_price = 0;
                
                if (!double.TryParse(parts[2].Trim(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out qtyDouble))
                {
                    Print("Quantidade invalida: " + parts[2]);
                    return;
                }
                
                if (parts.Length >= 4 && !string.IsNullOrEmpty(parts[3].Trim()))
                    double.TryParse(parts[3].Trim(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out tv_price);
                    
                if (parts.Length >= 5 && !string.IsNullOrEmpty(parts[4].Trim()))
                    double.TryParse(parts[4].Trim(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out sl_price);
                
                if (parts.Length >= 6 && !string.IsNullOrEmpty(parts[5].Trim()))
                    double.TryParse(parts[5].Trim(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out tp_price);
                
                // NT8 so aceita Int como Quantity
                int quantity = (int)Math.Round(qtyDouble); 
                if (quantity <= 0) quantity = 1;

                // === Resolver o instrumento alvo ===
                Instrument targetInstrument = null;
                try
                {
                    targetInstrument = Instrument.GetInstrument(targetSymbol);
                }
                catch (Exception ex)
                {
                    Print("⚠️ Erro ao resolver instrumento '" + targetSymbol + "': " + ex.Message);
                }

                if (targetInstrument == null)
                {
                    // Fallback: tenta usar o instrumento do gráfico
                    targetInstrument = Instrument;
                    Print("⚠️ Instrumento '" + targetSymbol + "' não encontrado. Usando gráfico atual: " + Instrument.FullName);
                }
                else
                {
                    Print("🔄 Instrumento resolvido: '" + targetSymbol + "' → " + targetInstrument.FullName);
                }

                // Obtém preço atual do gráfico (para Basis Offset)
                double currentPrice = Close[0];
                double currentAsk = currentPrice;
                double currentBid = currentPrice;
                try { 
                    double a = GetCurrentAsk(); if(a > 0) currentAsk = a; 
                    double b = GetCurrentBid(); if(b > 0) currentBid = b; 
                } catch { }

                double executionPrice = currentPrice;
                if (action == "BUY" || action == "LONG") executionPrice = currentAsk;
                else if (action == "SELL" || action == "SHORT") executionPrice = currentBid;

                LogBasis(string.Format("-> Comando {0} Recebido: Símbolo={1}, Lote={2}, TV_Price={3}, SL={4}, TP={5} | NT8 Exec={6}", action, targetSymbol, quantity, tv_price, sl_price, tp_price, executionPrice));

                // NinjaTrader Dynamic Millisecond Basis Offset
                if (tv_price > 0 && action != "CLOSE" && action != "STATUS" && action != "MODIFY") 
                {
                    lastCalculatedBasisOffset = executionPrice - tv_price;
                    LogBasis(string.Format("   [*] Basis Gap Recalculado: NT8_Exec({0}) - TV({1}) = Offset({2})", executionPrice, tv_price, lastCalculatedBasisOffset));
                }

                switch (action)
                {
                    case "BUY":
                    case "LONG":
                    {
                        Order mainOrder = tradingAccount.CreateOrder(
                            targetInstrument, OrderAction.Buy, OrderType.Market, OrderEntry.Manual,
                            TimeInForce.Gtc, quantity, 0, 0, "", "TV_Long_" + DateTime.Now.Ticks,
                            Core.Globals.MaxDate, null);
                        tradingAccount.Submit(new[] { mainOrder });
                        LogBasis(string.Format("   [->] BUY {0} contratos no {1} via Account.CreateOrder", quantity, targetInstrument.FullName));
                        break;
                    }
                    case "SELL":
                    case "SHORT":
                    {
                        Order mainOrder = tradingAccount.CreateOrder(
                            targetInstrument, OrderAction.SellShort, OrderType.Market, OrderEntry.Manual,
                            TimeInForce.Gtc, quantity, 0, 0, "", "TV_Short_" + DateTime.Now.Ticks,
                            Core.Globals.MaxDate, null);
                        tradingAccount.Submit(new[] { mainOrder });
                        LogBasis(string.Format("   [->] SHORT {0} contratos no {1} via Account.CreateOrder", quantity, targetInstrument.FullName));
                        break;
                    }
                    case "CLOSE":
                    {
                        // Flatten: fecha TODAS as posições do instrumento alvo na conta
                        tradingAccount.Flatten(new[] { targetInstrument });
                        Print("Fechando todas as posições de " + targetInstrument.FullName);
                        break;
                    }
                    default:
                        Print("Acao nao reconhecida: " + action);
                        break;
                }
            }
            catch (Exception ex)
            {
                Print("ERRO CRITICO em ExecuteTradeCommand: " + ex.Message + " | Stack: " + ex.StackTrace);
            }
        }

        private void ListenForClients()
        {
            try
            {
                tcpListener = new TcpListener(IPAddress.Any, Port);
                tcpListener.Start();

                while (isRunning)
                {
                    TcpClient client = tcpListener.AcceptTcpClient();
                    
                    Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
                    clientThread.IsBackground = true;
                    clientThread.Start(client);
                }
            }
            catch (SocketException ex)
            {
                if (isRunning) 
                    Print("SocketException no Listener: " + ex.Message);
            }
            catch (Exception ex)
            {
                Print("Exception no TCP Listener: " + ex.Message);
            }
        }

        private void HandleClientComm(object clientObj)
        {
            TcpClient tcpClient = (TcpClient)clientObj;
            try
            {
                NetworkStream clientStream = tcpClient.GetStream();
                using (StreamReader reader = new StreamReader(clientStream, new UTF8Encoding(false)))
                using (StreamWriter writer = new StreamWriter(clientStream, new UTF8Encoding(false)) { AutoFlush = true })
                {
                    string messageData = reader.ReadLine(); 
                    if (!string.IsNullOrEmpty(messageData))
                    {
                        LogBasis(string.Format(">>> TCP Recebeu (0ms de latência NT8): {0}", messageData));
                        if (messageData.Trim().ToUpper() == "STATUS")
                        {
                            string json = string.Format("{{\"account\":\"{0}\",\"balance\":{1},\"equity\":{2},\"instrument\":\"{3}\",\"tick_size\":{4},\"point_value\":{5},\"last_price\":{6}}}", 
                                cachedAccountName,
                                cachedBalance.ToString(System.Globalization.CultureInfo.InvariantCulture),
                                cachedEquity.ToString(System.Globalization.CultureInfo.InvariantCulture),
                                cachedInstrumentName,
                                cachedTickSize.ToString(System.Globalization.CultureInfo.InvariantCulture),
                                cachedPointValue.ToString(System.Globalization.CultureInfo.InvariantCulture),
                                cachedLastPrice.ToString(System.Globalization.CultureInfo.InvariantCulture));
                            writer.WriteLine(json);
                            return;
                        }

                        // Força a execução IMEDIATA na Thread Principal do NinjaTrader
                        if (ChartControl != null && ChartControl.Dispatcher != null)
                        {
                            ChartControl.Dispatcher.InvokeAsync(new Action(() => {
                                try
                                {
                                    UpdateThreadSafeCache();
                                    ExecuteTradeCommand(messageData);
                                }
                                catch (Exception ex)
                                {
                                    Print("Erro no InvokeAsync: " + ex.Message);
                                }
                            }));
                        }
                        else
                        {
                            // Fallback: enfileira para processamento no OnBarUpdate/OnMarketData
                            lock (threadLock)
                            {
                                pendingCommands.Add(messageData);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Print("Erro lendo do cliente: " + ex.Message);
            }
            finally
            {
                tcpClient.Close();
            }
        }
    }
}

#region AccountNameConverter
// TypeConverter que popula o dropdown com contas disponíveis no NT8
public class AccountNameConverter : TypeConverter
{
    public override bool GetStandardValuesSupported(ITypeDescriptorContext context) { return true; }
    public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) { return false; }
    
    public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
    {
        var accountNames = new List<string>();
        lock (Account.All)
        {
            foreach (Account acc in Account.All)
            {
                if (acc.Connection != null && acc.Connection.Status == ConnectionStatus.Connected)
                    accountNames.Add(acc.Name);
            }
        }
        return new StandardValuesCollection(accountNames);
    }
}
#endregion
