using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Haoliang.Models.Template;
using Haoliang.Models.Device;
using Haoliang.Models.DataCollection;
using Haoliang.Data.Repositories;

namespace Haoliang.Core.Services
{
    public interface ITagMappingService
    {
        Task<TagMapping> CreateTagMappingAsync(TagMapping mapping);
        Task<TagMapping> UpdateTagMappingAsync(int mappingId, TagMapping mapping);
        Task<bool> DeleteTagMappingAsync(int mappingId);
        Task<TagMapping> GetTagMappingByIdAsync(int mappingId);
        Task<IEnumerable<TagMapping>> GetAllTagMappingsAsync();
        Task<IEnumerable<TagMapping>> GetMappingsByTemplateAsync(int templateId);
        Task<TagMapping> MapDeviceTagAsync(TagData deviceTag, int templateId);
        Task<Dictionary<string, TagData>> MapDeviceTagsAsync(IEnumerable<TagData> deviceTags, int templateId);
        Task ValidateTagMappingAsync(TagMapping mapping);
        Task<IEnumerable<TagMapping>> GetMappingsByTagIdAsync(string tagId);
        Task<bool> IsTagMappedAsync(string tagId, int templateId);
        Task<TagMappingResult> ValidateAndMapTagsAsync(IEnumerable<TagData> deviceTags, int templateId);
    }

    public class TagMappingService : ITagMappingService
    {
        private readonly ITagMappingRepository _tagMappingRepository;
        private readonly ITemplateRepository _templateRepository;
        private readonly ILoggingService _loggingService;

        public TagMappingService(
            ITagMappingRepository tagMappingRepository,
            ITemplateRepository templateRepository,
            ILoggingService loggingService)
        {
            _tagMappingRepository = tagMappingRepository;
            _templateRepository = templateRepository;
            _loggingService = loggingService;
        }

        public async Task<TagMapping> CreateTagMappingAsync(TagMapping mapping)
        {
            // Validate mapping
            await ValidateTagMappingAsync(mapping);

            // Check if mapping already exists
            if (await IsTagMappedAsync(mapping.DeviceTagId, mapping.TemplateId))
            {
                throw new InvalidOperationException($"Tag {mapping.DeviceTagId} is already mapped for template {mapping.TemplateId}");
            }

            mapping.MappingId = 0; // Ensure new mapping
            mapping.CreatedAt = DateTime.Now;
            mapping.UpdatedAt = DateTime.Now;

            await _tagMappingRepository.AddAsync(mapping);
            await _tagMappingRepository.SaveAsync();

            await _loggingService.LogInfoAsync($"Created tag mapping: {mapping.DeviceTagId} -> {mapping.SystemTagId} for template {mapping.TemplateId}");
            return mapping;
        }

        public async Task<TagMapping> UpdateTagMappingAsync(int mappingId, TagMapping mapping)
        {
            var existingMapping = await _tagMappingRepository.GetByIdAsync(mappingId);
            if (existingMapping == null)
                throw new KeyNotFoundException($"Tag mapping with ID {mappingId} not found");

            // Validate updated mapping
            await ValidateTagMappingAsync(mapping);

            existingMapping.DeviceTagId = mapping.DeviceTagId;
            existingMapping.SystemTagId = mapping.SystemTagId;
            existingMapping.DataType = mapping.DataType;
            existingMapping.ConversionFormula = mapping.ConversionFormula;
            existingMapping.Description = mapping.Description;
            existingMapping.IsActive = mapping.IsActive;
            existingMapping.UpdatedAt = DateTime.Now;

            await _tagMappingRepository.UpdateAsync(existingMapping);
            await _tagMappingRepository.SaveAsync();

            await _loggingService.LogInfoAsync($"Updated tag mapping: {existingMapping.DeviceTagId} -> {existingMapping.SystemTagId}");
            return existingMapping;
        }

        public async Task<bool> DeleteTagMappingAsync(int mappingId)
        {
            var mapping = await _tagMappingRepository.GetByIdAsync(mappingId);
            if (mapping == null)
                return false;

            await _tagMappingRepository.DeleteAsync(mapping);
            await _tagMappingRepository.SaveAsync();

            await _loggingService.LogInfoAsync($"Deleted tag mapping: {mapping.DeviceTagId} -> {mapping.SystemTagId}");
            return true;
        }

        public async Task<TagMapping> GetTagMappingByIdAsync(int mappingId)
        {
            return await _tagMappingRepository.GetByIdAsync(mappingId);
        }

        public async Task<IEnumerable<TagMapping>> GetAllTagMappingsAsync()
        {
            return await _tagMappingRepository.GetAllAsync();
        }

        public async Task<IEnumerable<TagMapping>> GetMappingsByTemplateAsync(int templateId)
        {
            return await _tagMappingRepository.GetMappingsByTemplateAsync(templateId);
        }

        public async Task<TagMapping> MapDeviceTagAsync(TagData deviceTag, int templateId)
        {
            if (deviceTag == null)
                throw new ArgumentNullException(nameof(deviceTag));

            var template = await _templateRepository.GetByIdAsync(templateId);
            if (template == null)
                throw new KeyNotFoundException($"Template with ID {templateId} not found");

            // Find mapping for this device tag
            var mapping = await _tagMappingRepository.GetByDeviceTagAndTemplateAsync(deviceTag.Id, templateId);
            if (mapping == null)
            {
                await _loggingService.LogWarningAsync($"No mapping found for tag {deviceTag.Id} in template {templateId}");
                return null;
            }

            // Apply mapping and conversion
            var mappedTag = ApplyTagMapping(deviceTag, mapping);
            return mapping;
        }

        public async Task<Dictionary<string, TagData>> MapDeviceTagsAsync(IEnumerable<TagData> deviceTags, int templateId)
        {
            if (deviceTags == null)
                throw new ArgumentNullException(nameof(deviceTags));

            var template = await _templateRepository.GetByIdAsync(templateId);
            if (template == null)
                throw new KeyNotFoundException($"Template with ID {templateId} not found");

            var mappings = await GetMappingsByTemplateAsync(templateId);
            var mappingDict = mappings.ToDictionary(m => m.DeviceTagId);

            var result = new Dictionary<string, TagData>();
            
            foreach (var deviceTag in deviceTags)
            {
                if (mappingDict.ContainsKey(deviceTag.Id))
                {
                    var mapping = mappingDict[deviceTag.Id];
                    var mappedTag = ApplyTagMapping(deviceTag, mapping);
                    result[mapping.SystemTagId] = mappedTag;
                }
            }

            await _loggingService.LogInformationAsync($"Mapped {result.Count} tags from {deviceTags.Count()} device tags for template {templateId}");
            return result;
        }

        public async Task ValidateTagMappingAsync(TagMapping mapping)
        {
            if (mapping == null)
                throw new ArgumentNullException(nameof(mapping));

            if (string.IsNullOrWhiteSpace(mapping.DeviceTagId))
                throw new ArgumentException("Device tag ID is required");

            if (string.IsNullOrWhiteSpace(mapping.SystemTagId))
                throw new ArgumentException("System tag ID is required");

            // Validate template exists
            var template = await _templateRepository.GetByIdAsync(mapping.TemplateId);
            if (template == null)
                throw new KeyNotFoundException($"Template with ID {mapping.TemplateId} not found");

            // Validate data type
            if (!IsValidDataType(mapping.DataType))
                throw new ArgumentException($"Invalid data type: {mapping.DataType}");

            // Check for duplicate mappings
            var existingMapping = await _tagMappingRepository.GetByDeviceTagAndTemplateAsync(mapping.DeviceTagId, mapping.TemplateId);
            if (existingMapping != null && existingMapping.MappingId != mapping.MappingId)
            {
                throw new InvalidOperationException($"Duplicate mapping found for tag {mapping.DeviceTagId} in template {mapping.TemplateId}");
            }
        }

        public async Task<IEnumerable<TagMapping>> GetMappingsByTagIdAsync(string tagId)
        {
            return await _tagMappingRepository.GetMappingsByDeviceTagAsync(tagId);
        }

        public async Task<bool> IsTagMappedAsync(string tagId, int templateId)
        {
            var mapping = await _tagMappingRepository.GetByDeviceTagAndTemplateAsync(tagId, templateId);
            return mapping != null && mapping.IsActive;
        }

        public async Task<TagMappingResult> ValidateAndMapTagsAsync(IEnumerable<TagData> deviceTags, int templateId)
        {
            var result = new TagMappingResult
            {
                TemplateId = templateId,
                TotalDeviceTags = deviceTags.Count(),
                MappedTags = 0,
                UnmappedTags = new List<string>(),
                ConversionErrors = new List<string>(),
                MappedData = new Dictionary<string, TagData>()
            };

            var template = await _templateRepository.GetByIdAsync(templateId);
            if (template == null)
            {
                result.ConversionErrors.Add($"Template with ID {templateId} not found");
                return result;
            }

            var mappings = await GetMappingsByTemplateAsync(templateId);
            var mappingDict = mappings.ToDictionary(m => m.DeviceTagId);

            foreach (var deviceTag in deviceTags)
            {
                try
                {
                    if (mappingDict.ContainsKey(deviceTag.Id))
                    {
                        var mapping = mappingDict[deviceTag.Id];
                        if (mapping.IsActive)
                        {
                            var mappedTag = ApplyTagMapping(deviceTag, mapping);
                            result.MappedData[mapping.SystemTagId] = mappedTag;
                            result.MappedTags++;
                        }
                        else
                        {
                            result.UnmappedTags.Add(deviceTag.Id);
                        }
                    }
                    else
                    {
                        result.UnmappedTags.Add(deviceTag.Id);
                    }
                }
                catch (Exception ex)
                {
                    result.ConversionErrors.Add($"Failed to map tag {deviceTag.Id}: {ex.Message}");
                }
            }

            await _loggingService.LogInformationAsync($"Tag mapping validation: {result.MappedTags}/{result.TotalDeviceTags} tags mapped for template {templateId}");
            return result;
        }

        private TagData ApplyTagMapping(TagData deviceTag, TagMapping mapping)
        {
            var mappedTag = new TagData
            {
                Id = mapping.SystemTagId,
                Desc = mapping.Description ?? deviceTag.Desc,
                Quality = deviceTag.Quality,
                Time = deviceTag.Time
            };

            // Apply data type conversion
            mappedTag.Value = ConvertTagValue(deviceTag.Value, mapping.DataType, mapping.ConversionFormula);

            return mappedTag;
        }

        private object ConvertTagValue(object value, string dataType, string conversionFormula)
        {
            if (value == null)
                return null;

            try
            {
                // Apply conversion formula if provided
                if (!string.IsNullOrEmpty(conversionFormula))
                {
                    value = ApplyConversionFormula(value, conversionFormula);
                }

                // Convert to target data type
                switch (dataType.ToLower())
                {
                    case "int":
                        if (int.TryParse(value.ToString(), out int intValue))
                            return intValue;
                        throw new ArgumentException($"Cannot convert {value} to int");
                    
                    case "decimal":
                        if (decimal.TryParse(value.ToString(), out decimal decimalValue))
                            return decimalValue;
                        throw new ArgumentException($"Cannot convert {value} to decimal");
                    
                    case "bool":
                        if (bool.TryParse(value.ToString(), out bool boolValue))
                            return boolValue;
                        return Convert.ToBoolean(value);
                    
                    case "string":
                        return value.ToString();
                    
                    case "datetime":
                        if (DateTime.TryParse(value.ToString(), out DateTime dateTimeValue))
                            return dateTimeValue;
                        throw new ArgumentException($"Cannot convert {value} to datetime");
                    
                    default:
                        return value;
                }
            }
            catch (Exception ex)
            {
                throw new ArgumentException($"Failed to convert tag value: {ex.Message}");
            }
        }

        private object ApplyConversionFormula(object value, string formula)
        {
            // Simple formula evaluation (in real implementation, use expression parser)
            if (value == null || string.IsNullOrEmpty(formula))
                return value;

            var valueStr = value.ToString();
            
            // Handle basic arithmetic operations
            if (formula.Contains("*"))
            {
                var parts = formula.Split('*');
                if (parts.Length == 2 && double.TryParse(parts[1], out double factor))
                {
                    if (double.TryParse(valueStr, out double numericValue))
                        return numericValue * factor;
                }
            }
            else if (formula.Contains("/"))
            {
                var parts = formula.Split('/');
                if (parts.Length == 2 && double.TryParse(parts[1], out double divisor))
                {
                    if (double.TryParse(valueStr, out double numericValue) && divisor != 0)
                        return numericValue / divisor;
                }
            }
            else if (formula.Contains("+"))
            {
                var parts = formula.Split('+');
                if (parts.Length == 2 && double.TryParse(parts[1], out double offset))
                {
                    if (double.TryParse(valueStr, out double numericValue))
                        return numericValue + offset;
                }
            }
            else if (formula.Contains("-"))
            {
                var parts = formula.Split('-');
                if (parts.Length == 2 && double.TryParse(parts[1], out double offset))
                {
                    if (double.TryParse(valueStr, out double numericValue))
                        return numericValue - offset;
                }
            }

            return value;
        }

        private bool IsValidDataType(string dataType)
        {
            if (string.IsNullOrWhiteSpace(dataType))
                return false;

            var validTypes = new[] { "int", "decimal", "bool", "string", "datetime", "double" };
            return validTypes.Contains(dataType.ToLower());
        }
    }

    // Supporting classes and interfaces
    public class TagMappingResult
    {
        public int TemplateId { get; set; }
        public int TotalDeviceTags { get; set; }
        public int MappedTags { get; set; }
        public List<string> UnmappedTags { get; set; }
        public List<string> ConversionErrors { get; set; }
        public Dictionary<string, TagData> MappedData { get; set; }
    }

    // Additional repository interface for tag mappings
    public interface ITagMappingRepository : IRepository<TagMapping>
    {
        Task<IEnumerable<TagMapping>> GetMappingsByTemplateAsync(int templateId);
        Task<TagMapping> GetByDeviceTagAndTemplateAsync(string deviceTagId, int templateId);
        Task<IEnumerable<TagMapping>> GetMappingsByDeviceTagAsync(string deviceTagId);
        Task<IEnumerable<TagMapping>> GetActiveMappingsAsync();
        Task<bool> DeviceTagExistsAsync(string deviceTagId, int templateId);
    }
}