using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using HydraulicSimulator.Models;
using CtrEditor.ObjetosSim;

namespace CtrEditor.HydraulicSimulator
{
    /// <summary>
    /// Gestiona la simulación hidráulica similar a SimulationManagerBEPU
    /// </summary>
    public class HydraulicSimulationManager : IDisposable
    {
        #region Properties and Fields
        
        /// <summary>
        /// Red hidráulica principal que contiene todos los nodos y ramas
        /// </summary>
        public HydraulicNetwork Network { get; private set; }
        
        /// <summary>
        /// Lista de objetos simulables que tienen componentes hidráulicos
        /// </summary>
        public List<osBase> HydraulicObjects { get; private set; }
        
        /// <summary>
        /// Fluido utilizado en la simulación (agua por defecto)
        /// </summary>
        public Fluid SimulationFluid { get; set; }
        
        /// <summary>
        /// Resultado de la última simulación
        /// </summary>
        public SolutionResult LastSolutionResult { get; private set; }
        
        /// <summary>
        /// Tiempo global de la simulación hidráulica
        /// </summary>
        public float GlobalTime { get; private set; }
        
        /// <summary>
        /// Indica si la simulación hidráulica está habilitada
        /// </summary>
        public bool IsHydraulicSimulationEnabled { get; set; } = true;
        
        /// <summary>
        /// Parámetros del solver
        /// </summary>
        public int MaxIterations { get; set; } = 100;
        public double Tolerance { get; set; } = 1e-3;
        public double RelaxationFactor { get; set; } = 0.1;
        public bool VerboseOutput { get; set; } = false;
        
        /// <summary>
        /// Contador de pasos de simulación para optimizaciones
        /// </summary>
        private int _stepCount = 0;
        
        /// <summary>
        /// Cronómetro para medir tiempo de ejecución
        /// </summary>
        private Stopwatch _stopwatch;
        
        /// <summary>
        /// Diccionario para mapear IDs de objetos a elementos hidráulicos
        /// </summary>
        private Dictionary<string, osBase> _objectMapping;
        
        /// <summary>
        /// Indica si hay cambios pendientes en la red que requieren reconstrucción
        /// </summary>
        private bool _networkNeedsRebuild = true;
        
        #endregion
        
        #region Constructor
        
        /// <summary>
        /// Constructor del gestor de simulación hidráulica
        /// </summary>
        public HydraulicSimulationManager()
        {
            // Inicializar componentes
            SimulationFluid = Fluid.Water20C; // Agua a 20°C por defecto
            Network = new HydraulicNetwork(SimulationFluid);
            HydraulicObjects = new List<osBase>();
            _objectMapping = new Dictionary<string, osBase>();
            _stopwatch = new Stopwatch();
            
            // Inicializar resultado vacío
            LastSolutionResult = new SolutionResult(false)
            {
                ErrorMessage = "Simulación no ejecutada"
            };
            
            GlobalTime = 0.0f;
            
            Debug.WriteLine("HydraulicSimulationManager inicializado");
        }
        
        #endregion
        
        #region Object Management
        
        /// <summary>
        /// Registra un objeto simulable que tiene componentes hidráulicos
        /// </summary>
        /// <param name="hydraulicObject">Objeto con componentes hidráulicos</param>
        public void RegisterHydraulicObject(osBase hydraulicObject)
        {
            if (hydraulicObject == null)
                return;
                
            if (!HydraulicObjects.Contains(hydraulicObject))
            {
                HydraulicObjects.Add(hydraulicObject);
                _objectMapping[hydraulicObject.Nombre] = hydraulicObject;
                _networkNeedsRebuild = true;
                
                Debug.WriteLine($"Objeto hidráulico registrado: {hydraulicObject.Nombre}");
            }
        }
        
        /// <summary>
        /// Desregistra un objeto simulable hidráulico
        /// </summary>
        /// <param name="hydraulicObject">Objeto a desregistrar</param>
        public void UnregisterHydraulicObject(osBase hydraulicObject)
        {
            if (hydraulicObject == null)
                return;
                
            if (HydraulicObjects.Remove(hydraulicObject))
            {
                _objectMapping.Remove(hydraulicObject.Nombre);
                _networkNeedsRebuild = true;
                
                Debug.WriteLine($"Objeto hidráulico desregistrado: {hydraulicObject.Nombre}");
            }
        }
        
        /// <summary>
        /// Limpia todos los objetos hidráulicos registrados
        /// </summary>
        public void ClearHydraulicObjects()
        {
            HydraulicObjects.Clear();
            _objectMapping.Clear();
            _networkNeedsRebuild = true;
            
            Debug.WriteLine("Todos los objetos hidráulicos han sido limpiados");
        }
        
        #endregion
        
        #region Network Building
        
        /// <summary>
        /// Reconstruye la red hidráulica basada en los objetos registrados
        /// </summary>
        private void RebuildNetwork()
        {
            if (!_networkNeedsRebuild)
                return;
                
            Debug.WriteLine("Reconstruyendo red hidráulica...");
            
            // Crear nueva red
            Network = new HydraulicNetwork(SimulationFluid);
            
            // Agregar nodos y elementos basados en los objetos hidráulicos
            BuildNodesFromObjects();
            BuildBranchesFromObjects();
            
            _networkNeedsRebuild = false;
            
            Debug.WriteLine($"Red reconstruida: {Network.Nodes.Count} nodos, {Network.Branches.Count} ramas");
        }
        
        /// <summary>
        /// Construye los nodos de la red basándose en los objetos hidráulicos
        /// </summary>
        private void BuildNodesFromObjects()
        {
            foreach (var obj in HydraulicObjects)
            {
                // Verificar si el objeto implementa la interfaz hidráulica
                if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
                {
                    // Obtener nodos definidos por el objeto
                    var nodeDefinitions = hydraulicComponent.GetHydraulicNodes();
                    
                    foreach (var nodeDef in nodeDefinitions)
                    {
                        // Agregar nodo a la red
                        Network.AddNode(nodeDef.Name, nodeDef.IsFixedPressure ? nodeDef.Pressure : null);
                        
                        if (VerboseOutput)
                        {
                            Debug.WriteLine($"Nodo agregado: {nodeDef.Name} " +
                                          $"(Presión fija: {nodeDef.IsFixedPressure}, " +
                                          $"Presión: {nodeDef.Pressure?.ToString() ?? "libre"})");
                        }
                    }
                }
                else
                {
                    // Objeto sin interfaz hidráulica - crear nodos básicos por compatibilidad
                    string nodeName = $"Node_{obj.Nombre}";
                    bool isFixedPressure = DetermineIfFixedPressure(obj);
                    double? pressure = isFixedPressure ? GetObjectPressure(obj) : null;
                    
                    Network.AddNode(nodeName, pressure);
                }
            }
        }
        
        /// <summary>
        /// Construye las ramas (conexiones) entre nodos
        /// </summary>
        private void BuildBranchesFromObjects()
        {
            foreach (var obj in HydraulicObjects)
            {
                // Verificar si el objeto implementa la interfaz hidráulica
                if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
                {
                    // Obtener elementos definidos por el objeto
                    var elementDefinitions = hydraulicComponent.GetHydraulicElements();
                    
                    foreach (var elemDef in elementDefinitions)
                    {
                        // Crear rama con el elemento
                        var elements = new List<Element> { elemDef.Element };
                        Network.AddBranch(elemDef.FromNode, elemDef.ToNode, elements, elemDef.Name);
                        
                        if (VerboseOutput)
                        {
                            Debug.WriteLine($"Rama agregada: {elemDef.Name} " +
                                          $"({elemDef.FromNode} -> {elemDef.ToNode})");
                        }
                    }
                }
                else
                {
                    // Objeto sin interfaz hidráulica - crear elementos básicos por compatibilidad
                    var elements = CreateHydraulicElementsFromObject(obj);
                    
                    if (elements.Any())
                    {
                        string fromNode = GetSourceNode(obj);
                        string toNode = GetTargetNode(obj);
                        
                        if (!string.IsNullOrEmpty(fromNode) && !string.IsNullOrEmpty(toNode))
                        {
                            Network.AddBranch(fromNode, toNode, elements, $"Branch_{obj.Nombre}");
                        }
                    }
                }
            }
        }
        
        #endregion
        
        #region Helper Methods for Object Analysis
        
        /// <summary>
        /// Determina si un objeto debe tener presión fija
        /// </summary>
        private bool DetermineIfFixedPressure(osBase obj)
        {
            // Implementación que será extendida con tipos específicos
            // Por ejemplo: tanques, descargas atmosféricas = presión fija
            // Uniones, conexiones = presión libre
            
            return false; // Por defecto, presión libre
        }
        
        /// <summary>
        /// Obtiene la presión de un objeto (si tiene presión fija)
        /// </summary>
        private double GetObjectPressure(osBase obj)
        {
            // Implementación que será extendida
            return 0.0; // Por defecto, presión atmosférica
        }
        
        /// <summary>
        /// Crea elementos hidráulicos basándose en un objeto
        /// </summary>
        private List<Element> CreateHydraulicElementsFromObject(osBase obj)
        {
            var elements = new List<Element>();
            
            // Implementación que será extendida con tipos específicos
            // Por ejemplo:
            // - Si es una bomba: crear PumpHQ
            // - Si es una tubería: crear Pipe
            // - Si es una válvula: crear ValveKv
            
            return elements;
        }
        
        /// <summary>
        /// Obtiene el nodo fuente de un objeto
        /// </summary>
        private string GetSourceNode(osBase obj)
        {
            // Implementación que será extendida
            return $"Node_{obj.Nombre}_In";
        }
        
        /// <summary>
        /// Obtiene el nodo destino de un objeto
        /// </summary>
        private string GetTargetNode(osBase obj)
        {
            // Implementación que será extendida
            return $"Node_{obj.Nombre}_Out";
        }
        
        #endregion
        
        #region Simulation Step
        
        /// <summary>
        /// Ejecuta un paso de simulación hidráulica (equivalente al Step de BEPU)
        /// </summary>
        public void Step(float deltaTime = 0.016f) // ~60 FPS por defecto
        {
            if (!IsHydraulicSimulationEnabled)
                return;
                
            _stopwatch.Restart();
            
            try
            {
                // Actualizar tiempo global
                GlobalTime += deltaTime;
                _stepCount++;
                
                // Reconstruir red si es necesario
                RebuildNetwork();
                
                // Solo resolver si tenemos objetos hidráulicos
                if (HydraulicObjects.Count > 0 && Network.Branches.Count > 0)
                {
                    // Actualizar propiedades de objetos antes de resolver
                    UpdateObjectProperties();
                    
                    // Resolver la red hidráulica
                    LastSolutionResult = Network.Solve(
                        MaxIterations, 
                        Tolerance, 
                        RelaxationFactor, 
                        VerboseOutput
                    );
                    
                    if (LastSolutionResult.Converged)
                    {
                        // Aplicar resultados a los objetos
                        ApplyResultsToObjects();
                    }
                    else if (VerboseOutput)
                    {
                        Debug.WriteLine($"Simulación hidráulica no convergió: {LastSolutionResult.ErrorMessage}");
                    }
                }
                else
                {
                    // Sin objetos hidráulicos, resultado exitoso pero vacío
                    LastSolutionResult = new SolutionResult(true);
                }
            }
            catch (Exception ex)
            {
                LastSolutionResult = new SolutionResult(false)
                {
                    ErrorMessage = $"Error en simulación hidráulica: {ex.Message}"
                };
                
                Debug.WriteLine($"Error en HydraulicSimulationManager.Step: {ex}");
            }
            finally
            {
                _stopwatch.Stop();
                
                if (VerboseOutput && _stepCount % 60 == 0) // Log cada segundo aprox
                {
                    Debug.WriteLine($"Simulación hidráulica - Paso {_stepCount}: {_stopwatch.ElapsedMilliseconds}ms, " +
                                  $"Objetos: {HydraulicObjects.Count}, Convergió: {LastSolutionResult.Converged}");
                }
            }
        }
        
        /// <summary>
        /// Actualiza las propiedades de los objetos antes de resolver la red
        /// </summary>
        private void UpdateObjectProperties()
        {
            foreach (var obj in HydraulicObjects)
            {
                // Aquí se implementará la actualización de propiedades específicas
                // Por ejemplo: estado de válvulas, velocidad de bombas, etc.
                UpdateObjectHydraulicProperties(obj);
            }
        }
        
        /// <summary>
        /// Actualiza las propiedades hidráulicas específicas de un objeto
        /// </summary>
        private void UpdateObjectHydraulicProperties(osBase obj)
        {
            // Actualizar propiedades basándose en las interfaces implementadas
            if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
            {
                // Llamar al método de actualización del objeto
                hydraulicComponent.UpdateHydraulicProperties();
            }
            
            // Actualización específica por tipo de objeto
            if (obj is IHydraulicPump pump)
            {
                // Verificar estado de la bomba, velocidad, etc.
                UpdatePumpProperties(pump);
            }
            else if (obj is IHydraulicValve valve)
            {
                // Verificar apertura de la válvula
                UpdateValveProperties(valve);
            }
            else if (obj is IHydraulicTank tank)
            {
                // Actualizar nivel y presión del tanque
                UpdateTankProperties(tank);
            }
        }
        
        /// <summary>
        /// Actualiza propiedades específicas de bombas
        /// </summary>
        private void UpdatePumpProperties(IHydraulicPump pump)
        {
            // Aquí se pueden hacer validaciones y ajustes específicos de bombas
            // Por ejemplo, limitar velocidad, verificar estado de operación, etc.
            
            if (pump.SpeedRatio < 0.0) pump.SpeedRatio = 0.0;
            if (pump.SpeedRatio > 1.0) pump.SpeedRatio = 1.0;
            
            if (VerboseOutput)
            {
                Debug.WriteLine($"Bomba {pump.GetType().Name}: Velocidad={pump.SpeedRatio:F2}, " +
                              $"Funcionando={pump.IsRunning}, Dirección={pump.PumpDirection}");
            }
        }
        
        /// <summary>
        /// Actualiza propiedades específicas de válvulas
        /// </summary>
        private void UpdateValveProperties(IHydraulicValve valve)
        {
            // Validar apertura de válvula
            if (valve.Opening < 0.0) valve.Opening = 0.0;
            if (valve.Opening > 1.0) valve.Opening = 1.0;
            
            if (VerboseOutput)
            {
                Debug.WriteLine($"Válvula {valve.GetType().Name}: Apertura={valve.Opening:F2}, " +
                              $"Cerrada={valve.IsClosed}, Abierta={valve.IsFullyOpen}");
            }
        }
        
        /// <summary>
        /// Actualiza propiedades específicas de tanques
        /// </summary>
        private void UpdateTankProperties(IHydraulicTank tank)
        {
            // Calcular presión basada en nivel si no es presión fija
            if (!tank.IsFixedPressure)
            {
                // P = ρgh + P_atmosferica
                double pressureFromLevel = SimulationFluid.Rho * 9.80665 * tank.Level; // + presión atmosférica
                tank.TankPressure = pressureFromLevel;
            }
            
            if (VerboseOutput)
            {
                Debug.WriteLine($"Tanque {tank.GetType().Name}: Nivel={tank.Level:F2}m, " +
                              $"Presión={tank.TankPressure:F0}Pa, PresionFija={tank.IsFixedPressure}");
            }
        }
        
        /// <summary>
        /// Aplica los resultados de la simulación a los objetos
        /// </summary>
        private void ApplyResultsToObjects()
        {
            foreach (var obj in HydraulicObjects)
            {
                // Aplicar caudales y presiones calculados al objeto
                ApplyResultsToObject(obj);
            }
        }
        
        /// <summary>
        /// Aplica los resultados a un objeto específico
        /// </summary>
        private void ApplyResultsToObject(osBase obj)
        {
            // Aplicar resultados usando las interfaces hidráulicas
            if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
            {
                // Llamar al método de aplicación de resultados del objeto
                hydraulicComponent.ApplyHydraulicResults(LastSolutionResult.Flows, LastSolutionResult.Pressures);
            }
            
            // Aplicación específica por tipo de interfaz
            if (obj is IHydraulicFlowReceiver flowReceiver)
            {
                // Buscar caudal asociado al objeto
                string branchName = FindBranchNameForObject(obj);
                if (!string.IsNullOrEmpty(branchName) && LastSolutionResult.Flows.ContainsKey(branchName))
                {
                    double flow = LastSolutionResult.Flows[branchName];
                    flowReceiver.SetFlow(flow);
                }
            }
            
            if (obj is IHydraulicPressureReceiver pressureReceiver)
            {
                // Buscar presión asociada al objeto
                string nodeName = FindNodeNameForObject(obj);
                if (!string.IsNullOrEmpty(nodeName) && LastSolutionResult.Pressures.ContainsKey(nodeName))
                {
                    double pressure = LastSolutionResult.Pressures[nodeName];
                    pressureReceiver.SetPressure(pressure);
                }
            }
        }
        
        /// <summary>
        /// Encuentra el nombre de rama asociado a un objeto
        /// </summary>
        private string FindBranchNameForObject(osBase obj)
        {
            // Buscar en las ramas de la red
            foreach (var branch in Network.Branches)
            {
                if (branch.Name.Contains(obj.Nombre))
                {
                    return branch.Name;
                }
            }
            
            // Fallback a nombre por defecto
            return $"Branch_{obj.Nombre}";
        }
        
        /// <summary>
        /// Encuentra el nombre de nodo asociado a un objeto
        /// </summary>
        private string FindNodeNameForObject(osBase obj)
        {
            // Buscar en los nodos de la red
            foreach (var nodeKvp in Network.Nodes)
            {
                if (nodeKvp.Key.Contains(obj.Nombre))
                {
                    return nodeKvp.Key;
                }
            }
            
            // Fallback a nombre por defecto
            return $"Node_{obj.Nombre}";
        }
        
        /// <summary>
        /// Establece el caudal en un objeto
        /// </summary>
        private void SetObjectFlow(osBase obj, double flow)
        {
            if (obj is IHydraulicFlowReceiver flowReceiver)
            {
                flowReceiver.SetFlow(flow);
            }
        }
        
        /// <summary>
        /// Establece la presión en un objeto
        /// </summary>
        private void SetObjectPressure(osBase obj, double pressure)
        {
            if (obj is IHydraulicPressureReceiver pressureReceiver)
            {
                pressureReceiver.SetPressure(pressure);
            }
        }
        
        #endregion
        
        #region Public Methods
        
        /// <summary>
        /// Fuerza la reconstrucción de la red en el próximo paso
        /// </summary>
        public void InvalidateNetwork()
        {
            _networkNeedsRebuild = true;
        }
        
        /// <summary>
        /// Obtiene estadísticas de la simulación
        /// </summary>
        public string GetSimulationStats()
        {
            return $"Objetos hidráulicos: {HydraulicObjects.Count}\n" +
                   $"Nodos: {Network.Nodes.Count}\n" +
                   $"Ramas: {Network.Branches.Count}\n" +
                   $"Última simulación convergió: {LastSolutionResult.Converged}\n" +
                   $"Iteraciones: {LastSolutionResult.Iterations}\n" +
                   $"Residual: {LastSolutionResult.Residual:E3}\n" +
                   $"Tiempo global: {GlobalTime:F2}s\n" +
                   $"Pasos: {_stepCount}";
        }
        
        /// <summary>
        /// Reinicia la simulación
        /// </summary>
        public void Reset()
        {
            GlobalTime = 0.0f;
            _stepCount = 0;
            _networkNeedsRebuild = true;
            
            // Reinicializar resultado
            LastSolutionResult = new SolutionResult(false)
            {
                ErrorMessage = "Simulación reiniciada"
            };
            
            Debug.WriteLine("Simulación hidráulica reiniciada");
        }
        
        #endregion
        
        #region IDisposable
        
        private bool _disposed = false;
        
        /// <summary>
        /// Libera recursos del gestor de simulación
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        
        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    // Limpiar recursos gestionados
                    HydraulicObjects?.Clear();
                    _objectMapping?.Clear();
                    Network = null;
                    _stopwatch?.Stop();
                    
                    Debug.WriteLine("HydraulicSimulationManager disposed");
                }
                
                _disposed = true;
            }
        }
        
        #endregion
    }
}
