using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Haoliang.Models.Device;
using Haoliang.Models.DataCollection;
using Haoliang.Data.Repositories;
using Haoliang.Core.Services;

namespace Haoliang.Core.Services
{
    public class DeviceCollectionService : IDeviceCollectionService
    {
        private readonly IDeviceRepository _deviceRepository;
        private readonly ICollectionTaskRepository _taskRepository;
        private readonly ICollectionResultRepository _resultRepository;
        private readonly ICollectionLogRepository _logRepository;
        private readonly IPingService _pingService;
        private readonly IDataParserService _dataParserService;
        private readonly IDataStorageService _dataStorageService;
        private readonly IRetryService _retryService;
        private readonly HttpClient _httpClient;

        public DeviceCollectionService(
            IDeviceRepository deviceRepository,
            ICollectionTaskRepository taskRepository,
            ICollectionResultRepository resultRepository,
            ICollectionLogRepository logRepository,
            IPingService pingService,
            IDataParserService dataParserService,
            IDataStorageService dataStorageService,
            IRetryService retryService)
        {
            _deviceRepository = deviceRepository;
            _taskRepository = taskRepository;
            _resultRepository = resultRepository;
            _logRepository = logRepository;
            _pingService = pingService;
            _dataParserService = dataParserService;
            _dataStorageService = dataStorageService;
            _retryService = retryService;
            _httpClient = new HttpClient();
            _httpClient.Timeout = TimeSpan.FromSeconds(30);
        }

        public async Task CollectAllDevicesAsync()
        {
            var devices = await _deviceRepository.GetAllAsync();
            var onlineDevices = devices.Where(d => d.IsOnline && d.IsAvailable).ToList();

            foreach (var device in onlineDevices)
            {
                try
                {
                    await CollectDeviceAsync(device.Id);
                }
                catch (Exception ex)
                {
                    await LogCollectionAsync(device.Id, LogLevel.Error, 
                        $"Failed to collect from device {device.DeviceCode}: {ex.Message}");
                }
            }
        }

        public async Task CollectDeviceAsync(int deviceId)
        {
            var device = await _deviceRepository.GetByIdAsync(deviceId);
            if (device == null)
                throw new CollectionException(deviceId, "", "Device not found", CollectionErrorType.Unknown);

            if (!device.IsOnline || !device.IsAvailable)
            {
                throw new CollectionException(deviceId, device.DeviceCode, 
                    "Device is not online or not available", CollectionErrorType.DeviceOffline);
            }

            var taskId = await CreateCollectionTaskAsync(device);

            try
            {
                await _retryService.ExecuteWithRetryAsync(async () =>
                {
                    var result = await CollectDeviceDataAsync(deviceId);
                    await ProcessCollectedDataAsync(result);
                    await MarkTaskCompletedAsync(taskId, true);
                    return result;
                }, maxRetries: 3, delayMs: 30000);
            }
            catch (Exception ex)
            {
                await MarkTaskCompletedAsync(taskId, false, ex.Message);
                throw;
            }
        }

        public async Task<DeviceCurrentStatus> CollectDeviceDataAsync(int deviceId)
        {
            var device = await _deviceRepository.GetByIdAsync(deviceId);
            if (device == null)
                throw new CollectionException(deviceId, "", "Device not found", CollectionErrorType.Unknown);

            try
            {
                await _pingService.PingAsync(device.IPAddress);
                
                var rawJson = await GetDeviceDataAsync(device.HttpUrl);
                var result = await _dataParserService.ParseDeviceDataAsync(rawJson, deviceId);
                
                return result;
            }
            catch (Exception ex)
            {
                throw new CollectionException(deviceId, device.DeviceCode, 
                    ex.Message, CollectionErrorType.NetworkError);
            }
        }

        public async Task<bool> PingDeviceAsync(string ipAddress)
        {
            return await _pingService.PingAsync(ipAddress);
        }

        public async Task<string> GetDeviceDataAsync(string httpUrl)
        {
            try
            {
                var response = await _httpClient.GetAsync(httpUrl);
                response.EnsureSuccessStatusCode();
                
                var content = await response.Content.ReadAsStringAsync();
                return content;
            }
            catch (HttpRequestException ex)
            {
                throw new Exception($"HTTP request failed: {ex.Message}", ex);
            }
            catch (Exception ex)
            {
                throw new Exception($"Failed to get device data: {ex.Message}", ex);
            }
        }

        public async Task ProcessCollectedDataAsync(CollectionResult result)
        {
            if (result.IsSuccess && result.ParsedData != null)
            {
                await _dataStorageService.SaveDeviceStatusAsync(result.ParsedData);
                
                if (await _dataParserService.ValidateDeviceDataAsync(result.ParsedData))
                {
                    await UpdateDeviceStatusAsync(result.ParsedData);
                }
                else
                {
                    await LogCollectionAsync(result.DeviceId, LogLevel.Warning, 
                        "Device data validation failed", JsonSerializer.Serialize(result.ParsedData));
                }
            }
            else
            {
                await _dataStorageService.LogCollectionAsync(result.DeviceId, LogLevel.Error, 
                    "Collection failed", result.ErrorMessage);
            }
        }

        public async Task<IEnumerable<CollectionResult>> GetCollectionHistoryAsync(int deviceId, DateTime startDate, DateTime endDate)
        {
            return await _resultRepository.GetResultsByDateRangeAsync(startDate, endDate)
                .Where(r => r.DeviceId == deviceId).ToListAsync();
        }

        public async Task<CollectionStatistics> GetCollectionStatisticsAsync(DateTime date)
        {
            var startOfDay = date.Date;
            var endOfDay = startOfDay.AddDays(1);
            
            var results = await _resultRepository.GetResultsByDateRangeAsync(startOfDay, endOfDay);
            
            var stats = new CollectionStatistics
            {
                Date = date,
                TotalAttempts = results.Count(),
                SuccessCount = results.Count(r => r.IsSuccess),
                FailedCount = results.Count(r => !r.IsSuccess),
                SuccessRate = results.Any() ? (decimal)results.Count(r => r.IsSuccess) / results.Count() * 100 : 0,
                DeviceCount = results.Select(r => r.DeviceId).Distinct().Count(),
                OnlineDeviceCount = await _deviceRepository.CountOnlineDevicesAsync(),
                TotalDataSize = results.Sum(r => r.DataSize ?? 0)
            };

            if (stats.SuccessCount > 0)
            {
                var successfulResults = results.Where(r => r.IsSuccess).ToList();
                stats.AverageResponseTime = TimeSpan.FromTicks(
                    (long)successfulResults.Average(r => r.ResponseTime ?? 0));
            }

            return stats;
        }

        public async Task<CollectionHealth> GetCollectionHealthAsync()
        {
            var stats = await GetCollectionStatisticsAsync(DateTime.Now);
            var onlineDeviceCount = await _deviceRepository.CountOnlineDevicesAsync();
            var availableDeviceCount = await _deviceRepository.CountAvailableDevicesAsync();
            
            var activeTasks = await _taskRepository.GetRunningTasksAsync();
            var failedTasks = await _taskRepository.GetFailedTasksAsync();
            
            var lastSuccessful = await _resultRepository.GetResultsByDateRangeAsync(
                DateTime.Now.AddDays(-1), DateTime.Now)
                .Where(r => r.IsSuccess).FirstOrDefault();
            
            var lastFailed = await _resultRepository.GetResultsByDateRangeAsync(
                DateTime.Now.AddDays(-1), DateTime.Now)
                .Where(r => !r.IsSuccess).FirstOrDefault();

            return new CollectionHealth
            {
                CheckTime = DateTime.Now,
                TotalDevices = onlineDeviceCount,
                OnlineDevices = availableDeviceCount,
                ActiveCollectionTasks = activeTasks.Count(),
                FailedTasks = failedTasks.Count(),
                SuccessRate = stats.SuccessRate,
                AverageResponseTime = stats.AverageResponseTime,
                TotalCollectedData = stats.TotalDataSize,
                LastSuccessfulCollection = lastSuccessful?.CollectionTime ?? DateTime.MinValue,
                LastFailedCollection = lastFailed?.CollectionTime ?? DateTime.MinValue
            };
        }

        public async Task RestartFailedCollectionsAsync()
        {
            var failedTasks = await _taskRepository.GetFailedTasksAsync();
            
            foreach (var task in failedTasks)
            {
                try
                {
                    await CollectDeviceAsync(task.DeviceId);
                }
                catch (Exception ex)
                {
                    await LogCollectionAsync(task.DeviceId, LogLevel.Error, 
                        $"Failed to restart collection for device {task.DeviceId}: {ex.Message}");
                }
            }
        }

        public async Task<bool> TestConnectionAsync(int deviceId)
        {
            try
            {
                var device = await _deviceRepository.GetByIdAsync(deviceId);
                if (device == null)
                    return false;

                var isOnline = await _pingService.PingAsync(device.IPAddress);
                if (!isOnline)
                    return false;

                var data = await GetDeviceDataAsync(device.HttpUrl);
                return !string.IsNullOrEmpty(data);
            }
            catch
            {
                return false;
            }
        }

        private async Task<int> CreateCollectionTaskAsync(CNCDevice device)
        {
            var task = new CollectionTask
            {
                DeviceId = device.Id,
                TaskName = $"Collection_{device.DeviceCode}_{DateTime.Now:yyyyMMddHHmmss}",
                Status = "Pending",
                ScheduledTime = DateTime.Now,
                CreatedAt = DateTime.Now
            };

            await _taskRepository.AddAsync(task);
            await _taskRepository.SaveAsync();
            return task.Id;
        }

        private async Task MarkTaskCompletedAsync(int taskId, bool isSuccess, string errorMessage = null)
        {
            await _taskRepository.MarkTaskCompletedAsync(taskId, isSuccess, errorMessage);
        }

        private async Task UpdateDeviceStatusAsync(DeviceCurrentStatus status)
        {
            var device = await _deviceRepository.GetByIdAsync(status.DeviceId);
            if (device != null)
            {
                device.IsOnline = true;
                device.LastCollectionTime = status.RecordTime;
                await _deviceRepository.SaveAsync();
            }
        }

        private async Task LogCollectionAsync(int deviceId, LogLevel logLevel, string message, string data = null)
        {
            await _dataStorageService.LogCollectionAsync(deviceId, logLevel, message, data);
        }
    }

    public class PingService : IPingService
    {
        public async Task<bool> PingAsync(string ipAddress)
        {
            try
            {
                using (var client = new System.Net.NetworkInformation.Ping())
                {
                    var reply = await client.SendPingAsync(ipAddress, 3000);
                    return reply.Status == System.Net.NetworkInformation.IPStatus.Success;
                }
            }
            catch
            {
                return false;
            }
        }

        public async Task<int> GetPingTimeAsync(string ipAddress)
        {
            try
            {
                using (var client = new System.Net.NetworkInformation.Ping())
                {
                    var reply = await client.SendPingAsync(ipAddress, 3000);
                    return reply.Status == System.Net.NetworkInformation.IPStatus.Success ? 
                        reply.RoundtripTime : -1;
                }
            }
            catch
            {
                return -1;
            }
        }

        public async Task<bool> IsDeviceOnlineAsync(string ipAddress)
        {
            return await PingAsync(ipAddress);
        }

        public async Task<PingResult> GetPingResultAsync(string ipAddress)
        {
            try
            {
                using (var client = new System.Net.NetworkInformation.Ping())
                {
                    var reply = await client.SendPingAsync(ipAddress, 3000);
                    return new PingResult
                    {
                        IsSuccess = reply.Status == System.Net.NetworkInformation.IPStatus.Success,
                        PingTime = reply.RoundtripTime,
                        PingTime = DateTime.Now
                    };
                }
            }
            catch (Exception ex)
            {
                return new PingResult
                {
                    IsSuccess = false,
                    ErrorMessage = ex.Message,
                    PingTime = DateTime.Now
                };
            }
        }
    }

    public class DataParserService : IDataParserService
    {
        public async Task<DeviceCurrentStatus> ParseDeviceDataAsync(string rawJson, int deviceId)
        {
            try
            {
                var document = JsonDocument.Parse(rawJson);
                var root = document.RootElement;
                
                var device = new DeviceCurrentStatus
                {
                    DeviceId = deviceId,
                    RecordTime = DateTime.Now,
                    Tags = new List<TagData>()
                };

                if (root.TryGetProperty("device", out var deviceElement))
                {
                    device.DeviceCode = deviceElement.GetString();
                }

                if (root.TryGetProperty("desc", out var descElement))
                {
                    device.DeviceName = descElement.GetString();
                }

                if (root.TryGetProperty("tags", out var tagsElement))
                {
                    foreach (var tagElement in tagsElement.EnumerateArray())
                    {
                        var tag = new TagData
                        {
                            Id = tagElement.GetProperty("id").GetString(),
                            Desc = tagElement.GetProperty("desc").GetString(),
                            Quality = tagElement.GetProperty("quality").GetString(),
                            Time = DateTime.Parse(tagElement.GetProperty("time").GetString())
                        };

                        if (tagElement.TryGetProperty("value", out var valueElement))
                        {
                            tag.Value = ParseTagValue(valueElement);
                        }

                        device.Tags.Add(tag);
                    }
                }

                ExtractDeviceStatus(device);
                return device;
            }
            catch (Exception ex)
            {
                throw new Exception($"Failed to parse device data: {ex.Message}", ex);
            }
        }

        private void ExtractDeviceStatus(DeviceCurrentStatus device)
        {
            var ioStatus = device.Tags?.FirstOrDefault(t => t.Id == "_io_status");
            var tag9 = device.Tags?.FirstOrDefault(t => t.Id == "Tag9");
            var tag26 = device.Tags?.FirstOrDefault(t => t.Id == "Tag26");

            device.Status = "Unknown";
            device.IsRunning = false;

            if (ioStatus?.Value?.ToString() == "1" || tag9?.Value?.ToString() == "1" || tag26?.Value?.ToString() == "1")
            {
                device.Status = "Running";
                device.IsRunning = true;
            }
            else
            {
                device.Status = "Stopped";
                device.IsRunning = false;
            }

            var ncProgram = device.Tags?.FirstOrDefault(t => t.Id == "Tag5");
            device.NCProgram = ncProgram?.Value?.ToString();

            var cumulativeCount = device.Tags?.FirstOrDefault(t => t.Id == "Tag8");
            if (cumulativeCount?.Value != null)
            {
                if (int.TryParse(cumulativeCount.Value.ToString(), out int count))
                {
                    device.CumulativeCount = count;
                }
            }

            var operatingMode = device.Tags?.FirstOrDefault(t => t.Id == "Tag11");
            device.OperatingMode = operatingMode?.Value?.ToString();
        }

        private object ParseTagValue(JsonElement valueElement)
        {
            if (valueElement.ValueKind == JsonValueKind.String)
            {
                return valueElement.GetString();
            }
            else if (valueElement.ValueKind == JsonValueKind.Number)
            {
                if (valueElement.TryGetInt32(out int intValue))
                    return intValue;
                else if (valueElement.TryGetDecimal(out decimal decimalValue))
                    return decimalValue;
                else
                    return valueElement.GetDouble();
            }
            else if (valueElement.ValueKind == JsonValueKind.True || valueElement.ValueKind == JsonValueKind.False)
            {
                return valueElement.GetBoolean();
            }
            else
            {
                return valueElement.ToString();
            }
        }

        public async Task<TagData> ParseTagDataAsync(object tagValue, string dataType)
        {
            var tag = new TagData();
            
            switch (dataType.ToLower())
            {
                case "int":
                    if (int.TryParse(tagValue?.ToString(), out int intValue))
                        tag.Value = intValue;
                    break;
                case "decimal":
                    if (decimal.TryParse(tagValue?.ToString(), out decimal decimalValue))
                        tag.Value = decimalValue;
                    break;
                case "bool":
                    if (bool.TryParse(tagValue?.ToString(), out bool boolValue))
                        tag.Value = boolValue;
                    break;
                default:
                    tag.Value = tagValue?.ToString();
                    break;
            }

            return tag;
        }

        public async Task<bool> ValidateDeviceDataAsync(DeviceCurrentStatus data)
        {
            if (data == null)
                return false;

            if (string.IsNullOrEmpty(data.DeviceCode))
                return false;

            if (data.Tags == null || !data.Tags.Any())
                return false;

            if (data.CumulativeCount < 0)
                return false;

            return true;
        }

        public async Task<string> ConvertDataFormatAsync(object value, string dataType)
        {
            switch (dataType.ToLower())
            {
                case "int":
                    if (int.TryParse(value?.ToString(), out int intValue))
                        return intValue.ToString();
                    break;
                case "decimal":
                    if (decimal.TryParse(value?.ToString(), out decimal decimalValue))
                        return decimalValue.ToString("F2");
                    break;
                case "bool":
                    if (bool.TryParse(value?.ToString(), out bool boolValue))
                        return boolValue.ToString();
                    break;
                default:
                    return value?.ToString() ?? "";
            }

            return "";
        }
    }

    public class DataStorageService : IDataStorageService
    {
        private readonly ICollectionResultRepository _resultRepository;
        private readonly IDeviceStatusRepository _deviceStatusRepository;
        private readonly ICollectionLogRepository _logRepository;

        public DataStorageService(
            ICollectionResultRepository resultRepository,
            IDeviceStatusRepository deviceStatusRepository,
            ICollectionLogRepository logRepository)
        {
            _resultRepository = resultRepository;
            _deviceStatusRepository = deviceStatusRepository;
            _logRepository = logRepository;
        }

        public async Task SaveCollectionResultAsync(CollectionResult result)
        {
            await _resultRepository.AddAsync(result);
            await _resultRepository.SaveAsync();
        }

        public async Task SaveDeviceStatusAsync(DeviceCurrentStatus status)
        {
            var deviceStatus = new DeviceStatus
            {
                DeviceId = status.DeviceId,
                Status = status.Status,
                IsRunning = status.IsRunning,
                NCProgram = status.NCProgram,
                CumulativeCount = status.CumulativeCount,
                OperatingMode = status.OperatingMode,
                RecordTime = status.RecordTime
            };

            await _deviceStatusRepository.AddAsync(deviceStatus);
            await _deviceStatusRepository.SaveAsync();
        }

        public async Task SaveRawDataAsync(int deviceId, string rawJson, bool isSuccess, string errorMessage = null)
        {
            var result = new CollectionResult
            {
                DeviceId = deviceId,
                RawJson = rawJson,
                IsSuccess = isSuccess,
                ErrorMessage = errorMessage,
                CollectionTime = DateTime.Now,
                CreatedAt = DateTime.Now
            };

            await _resultRepository.AddAsync(result);
            await _resultRepository.SaveAsync();
        }

        public async Task LogCollectionAsync(int deviceId, LogLevel logLevel, string message, string data = null)
        {
            var log = new CollectionLog
            {
                DeviceId = deviceId,
                LogLevel = logLevel.ToString(),
                LogCategory = "Collection",
                LogMessage = message,
                LogData = data,
                LogTime = DateTime.Now,
                CreatedAt = DateTime.Now
            };

            await _logRepository.AddAsync(log);
            await _logRepository.SaveAsync();
        }

        public async Task ArchiveOldDataAsync(int daysToKeep = 30)
        {
            await _resultRepository.DeleteOldResultsAsync(daysToKeep);
            await _logRepository.DeleteOldLogsAsync(daysToKeep);
        }
    }

    public class RetryService : IRetryService
    {
        public async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> operation, int maxRetries = 3, int delayMs = 30000)
        {
            Exception lastException = null;
            
            for (int attempt = 1; attempt <= maxRetries; attempt++)
            {
                try
                {
                    return await operation();
                }
                catch (Exception ex) when (attempt < maxRetries)
                {
                    lastException = ex;
                    if (ShouldRetry(ex, attempt))
                    {
                        await Task.Delay(delayMs);
                    }
                    else
                    {
                        throw;
                    }
                }
            }

            throw lastException ?? new Exception("Operation failed after retries");
        }

        public async Task ExecuteWithRetryAsync(Func<Task> operation, int maxRetries = 3, int delayMs = 30000)
        {
            await ExecuteWithRetryAsync(async () =>
            {
                await operation();
                return true;
            }, maxRetries, delayMs);
        }

        public bool ShouldRetry(Exception ex, int attemptNumber)
        {
            // Retry on network-related exceptions
            if (ex is HttpRequestException || 
                ex is System.Net.Sockets.SocketException ||
                ex is System.TimeoutException)
            {
                return true;
            }

            // Retry on certain specific exceptions
            if (ex.Message.Contains("timeout") || 
                ex.Message.Contains("network") ||
                ex.Message.Contains("connection"))
            {
                return true;
            }

            // Don't retry after max attempts
            if (attemptNumber >= 3)
            {
                return false;
            }

            return false;
        }
    }
}