using Amazon;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.SimpleSystemsManagement;
using Amazon.SimpleSystemsManagement.Model;
using Azure.Core;
using DataBase.Entities.Models;
using Microsoft.EntityFrameworkCore;
using MySqlConnector;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using User.Insight.Tracker.DataContracts;
using User.Insight.Tracker.DataContracts.Requests;
using User.Insight.Tracker.DataContracts.Response;
using User.Insight.Tracker.DataProviders;

namespace User.Insight.Tracker.Services
{
    [ExcludeFromCodeCoverage]
    public class S3BucketService : IS3BucketService
    {
        private readonly AmazonS3Client _s3Client;
        private readonly ComputraceqaContext _dbContext;
        private readonly IAzureSyncingServiceProvider _azureServiceProvider;
        private string _bucketName;
        private static readonly RegionEndpoint _region = RegionEndpoint.GetBySystemName("ap-south-1");
        private static readonly GetParameterRequest _bucketParameterRequest = new()
        {
            Name = "/qa/userinsighttracking/s3bucketname",
            WithDecryption = true
        };

        public S3BucketService(ComputraceqaContext dbContext, IAzureSyncingServiceProvider azureServiceProvider)
        {          
            _dbContext = dbContext;
            _azureServiceProvider = azureServiceProvider;
            _s3Client = new AmazonS3Client(new AmazonS3Config { RegionEndpoint = _region });
        }

        [ExcludeFromCodeCoverage]
        public async Task<PageResponse<S3BucketResponse>> GetImageUrlsFromS3Async(
            SearchRequest<S3BucketRequest> s3Bucket, JwtTokenDetail userDetails)
        {
            // Step 0: Optimize SearchText to EmployeeId conversion
            if (!string.IsNullOrEmpty(s3Bucket.SearchText))
            {
                await UpdateFilterWithEmployeeIdAsync(s3Bucket);
            }

            // Get employee hierarchy data
            var employeeHirs = _azureServiceProvider.GetEmployeeCallDetails(userDetails);

            // Get all images from S3
            var imageInfos = await GetAllImagesFromS3Async(s3Bucket, employeeHirs);

            if (imageInfos.Count == 0)
            {
                return CreateEmptyPageResponse(s3Bucket);
            }

            // Get bucket name if not already cached
            await EnsureBucketNameAsync();

            // Process images and generate pre-signed URLs
            var (listResponse, empCodes) = await ProcessImageInfosAsync(imageInfos);

            // Get employee details in a single batch
            var employeeDetails = await GetEmployeeDetailsBatchAsync(empCodes);

            // Build final response
            var finalResponses = BuildS3BucketResponses(listResponse, employeeDetails);

            // Apply sorting and filtering
            finalResponses = ApplySortingAndFiltering(s3Bucket, finalResponses);

            // Apply pagination
            return CreatePagedResponse(finalResponses, s3Bucket);
        }

        private async Task UpdateFilterWithEmployeeIdAsync(SearchRequest<S3BucketRequest> s3Bucket)
        {
            var searchText = s3Bucket.SearchText.ToLowerInvariant();

            var matchedEmployeeId = await _dbContext.EmployeeDetails
                .Where(e => e.FirstName.ToLower().Contains(searchText) ||
                           e.LastName.ToLower().Contains(searchText) ||
                           e.DisplayName.ToLower().Contains(searchText))
                .Select(e => e.EmployeeId)
                .FirstOrDefaultAsync();

            if (!string.IsNullOrEmpty(matchedEmployeeId))
            {
                s3Bucket.Filter.EmployeeId = matchedEmployeeId;
            }
        }

        private static PageResponse<S3BucketResponse> CreateEmptyPageResponse(SearchRequest<S3BucketRequest> s3Bucket)
        {
            return new PageResponse<S3BucketResponse>
            {
                Records = new List<S3BucketResponse>(),
                TotalRecords = 0,
                TotalPages = 0,
                PageSize = s3Bucket.PageSize,
                CurrentPage = s3Bucket.PageNumber
            };
        }

        private async Task EnsureBucketNameAsync()
        {
            if (string.IsNullOrEmpty(_bucketName))
            {
                using var ssmClient = new AmazonSimpleSystemsManagementClient(_region);
                var response = await ssmClient.GetParameterAsync(_bucketParameterRequest);
                _bucketName = response.Parameter.Value;
            }
        }

        private Task<(List<S3ImageInfo> imageInfos, HashSet<string> empCodes)> ProcessImageInfosAsync(
        List<S3ImageInfo> imageInfos)
        {
            var empCodes = new HashSet<string>();
            var processedInfos = new List<S3ImageInfo>(imageInfos.Count);

            foreach (var imageInfo in imageInfos)
            {
                var segments = imageInfo.Key.Split('/', StringSplitOptions.RemoveEmptyEntries);
                if (TryParseImageKey(segments, out string empCode, out _, out _) && !string.IsNullOrEmpty(empCode))
                {
                    empCodes.Add(empCode);

                    imageInfo.PreSignedUrl = _s3Client.GetPreSignedURL(new GetPreSignedUrlRequest
                    {
                        BucketName = _bucketName,
                        Key = imageInfo.Key,
                        Expires = DateTime.UtcNow.AddMinutes(10)
                    });

                    processedInfos.Add(imageInfo);
                }
            }

            return Task.FromResult((processedInfos, empCodes));
        }

        private async Task<Dictionary<string, string>> GetEmployeeDetailsBatchAsync(HashSet<string> empCodes)
        {
            if (empCodes.Count == 0)
            {
                return new Dictionary<string, string>();
            }

            return await _dbContext.EmployeeDetails
                .Where(e => empCodes.Contains(e.EmployeeId))
                .Select(e => new { e.EmployeeId, e.DisplayName })
                .ToDictionaryAsync(e => e.EmployeeId, e => e.DisplayName);
        }

        private List<S3BucketResponse> BuildS3BucketResponses(
            List<S3ImageInfo> imageInfos,
            Dictionary<string, string> employeeDetails)
        {
            var responses = new List<S3BucketResponse>(imageInfos.Count);

            foreach (var imageInfo in imageInfos)
            {
                var segments = imageInfo.Key.Split('/', StringSplitOptions.RemoveEmptyEntries);
                if (TryParseImageKey(segments, out string empCode, out string appName, out DateTime? parsedDate))
                {
                    employeeDetails.TryGetValue(empCode, out string empName);

                    responses.Add(new S3BucketResponse
                    {
                        Picture = imageInfo.PreSignedUrl,
                        NameApplication = appName,
                        Date = parsedDate,
                        EmpCode = empCode,
                        EmployeeName = empName
                    });
                }
            }

            return responses;
        }

        private static bool TryParseImageKey(string[] segments, out string empCode, out string appName, out DateTime? parsedDate)
        {
            empCode = string.Empty;
            appName = string.Empty;
            parsedDate = null;

            if (segments.Length < 3) return false;

            empCode = segments[0];

            // Parse date
            if (segments[1].Length == 8 &&
            DateTime.TryParseExact(segments[1], "ddMMyyyy",
            CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime date))
            {
                parsedDate = date;
            }

            // Parse application name from filename
            var fileName = Path.GetFileNameWithoutExtension(segments[2]);
            var lastHyphenIndex = fileName.LastIndexOf('-');
            appName = lastHyphenIndex > 0
                ? fileName[..lastHyphenIndex]
                : fileName;

            return true;
        }

        private static List<S3BucketResponse> ApplySortingAndFiltering(
    SearchRequest<S3BucketRequest> request,
    List<S3BucketResponse> responses)
        {
            if (responses.Count == 0) return responses;

            responses = ApplySearchFilter(request, responses);
            responses = ApplySorting(request, responses);

            return responses;
        }

        private static List<S3BucketResponse> ApplySearchFilter(
            SearchRequest<S3BucketRequest> request,
            List<S3BucketResponse> responses)
        {
            if (string.IsNullOrWhiteSpace(request.SearchText))
                return responses;

            var searchLower = request.SearchText.ToLowerInvariant();

            return responses.Where(r => MatchesSearchCriteria(r, searchLower)).ToList();
        }

        private static bool MatchesSearchCriteria(S3BucketResponse response, string searchLower)
        {
            return (!string.IsNullOrEmpty(response.EmpCode) &&
                    response.EmpCode.Contains(searchLower, StringComparison.OrdinalIgnoreCase)) ||
                   (!string.IsNullOrEmpty(response.NameApplication) &&
                    response.NameApplication.Contains(searchLower, StringComparison.OrdinalIgnoreCase)) ||
                   (!string.IsNullOrEmpty(response.EmployeeName) &&
                    response.EmployeeName.Contains(searchLower, StringComparison.OrdinalIgnoreCase));
        }

        private static List<S3BucketResponse> ApplySorting(
            SearchRequest<S3BucketRequest> request,
            List<S3BucketResponse> responses)
        {
            if (string.IsNullOrWhiteSpace(request.SortColumn))
                return responses;

            var isDescending = IsDescendingSortOrder(request.SortOrder);
            var sortColumn = request.SortColumn.ToLowerInvariant();

            return SortResponses(responses, sortColumn, isDescending);
        }

        private static bool IsDescendingSortOrder(string sortOrder)
        {
            return sortOrder?.ToLowerInvariant() is "desc" or "descending";
        }

        private static List<S3BucketResponse> SortResponses(
            List<S3BucketResponse> responses,
            string sortColumn,
            bool isDescending)
        {
            return sortColumn switch
            {
                "employeename" => SortByEmployeeName(responses, isDescending),
                "nameapplication" => SortByApplicationName(responses, isDescending),
                "date" => SortByDate(responses, isDescending),
                _ => responses
            };
        }

        private static List<S3BucketResponse> SortByEmployeeName(
            List<S3BucketResponse> responses,
            bool isDescending)
        {
            return isDescending
                ? responses.OrderByDescending(x => x.EmpCode).ToList()
                : responses.OrderBy(x => x.EmpCode).ToList();
        }

        private static List<S3BucketResponse> SortByApplicationName(
            List<S3BucketResponse> responses,
            bool isDescending)
        {
            return isDescending
                ? responses.OrderByDescending(x => x.NameApplication).ToList()
                : responses.OrderBy(x => x.NameApplication).ToList();
        }

        private static List<S3BucketResponse> SortByDate(
            List<S3BucketResponse> responses,
            bool isDescending)
        {
            return isDescending
                ? responses.OrderByDescending(x => x.Date).ToList()
                : responses.OrderBy(x => x.Date).ToList();
        }

        private static PageResponse<S3BucketResponse> CreatePagedResponse(
            List<S3BucketResponse> responses,
            SearchRequest<S3BucketRequest> request)
        {
            var totalRecords = responses.Count;
            var totalPages = (int)Math.Ceiling((double)totalRecords / request.PageSize);

            var pagedRecords = responses
                .Skip((request.PageNumber - 1) * request.PageSize)
                .Take(request.PageSize)
                .ToList();

            return new PageResponse<S3BucketResponse>
            {
                Records = pagedRecords,
                TotalRecords = totalRecords,
                TotalPages = totalPages,
                PageSize = request.PageSize,
                CurrentPage = request.PageNumber
            };
        }

        [ExcludeFromCodeCoverage]
        public async Task<List<S3ImageInfo>> GetAllImagesFromS3Async(
            SearchRequest<S3BucketRequest> s3Bucket,
            List<EmployeeHir> employeeHirs)
        {
            var filter = s3Bucket.Filter;
            if (filter == null) return new List<S3ImageInfo>();

            // Determine employee IDs
            var employeeIds = GetEmployeeIds(filter, employeeHirs);
            if (employeeIds.Count == 0) return new List<S3ImageInfo>();

            // Ensure bucket name is loaded
            await EnsureBucketNameAsync();

            var formattedDate = filter.Date?.ToString("ddMMyyyy") ?? string.Empty;
            var result = new List<S3ImageInfo>();

            // Process all employees in parallel for better performance
            var tasks = employeeIds.Select(empId =>
                ListObjectsForEmployeeAsync(empId, formattedDate));

            var results = await Task.WhenAll(tasks);

            foreach (var imageInfos in results.Where(r => r != null))
            {
                result.AddRange(imageInfos);
            }

            return result;
        }

        private List<string> GetEmployeeIds(S3BucketRequest filter, List<EmployeeHir> employeeHirs)
        {
            if (!string.IsNullOrWhiteSpace(filter.EmployeeId))
            {
                return employeeHirs.Any(e => e.EmpCode == filter.EmployeeId)
                    ? new List<string> { filter.EmployeeId }
                    : new List<string>();
            }

            return employeeHirs
                .Select(e => e.EmpCode)
                .Where(e => !string.IsNullOrWhiteSpace(e))
                .Distinct()
                .ToList();
        }

        private async Task<List<S3ImageInfo>> ListObjectsForEmployeeAsync(string empId, string formattedDate)
        {
            var result = new List<S3ImageInfo>();
            var prefix = string.IsNullOrEmpty(formattedDate) ? empId : $"{empId}/{formattedDate}";

            var listRequest = new ListObjectsV2Request
            {
                BucketName = _bucketName,
                Prefix = prefix,
                MaxKeys = 1000 // Optimize for batch retrieval
            };

            ListObjectsV2Response listResponse;

            do
            {
                listResponse = await _s3Client.ListObjectsV2Async(listRequest);

                // Filter and add objects
                var filteredObjects = string.IsNullOrEmpty(formattedDate)
                    ? listResponse.S3Objects
                    : listResponse.S3Objects.Where(obj =>
                        obj.LastModified.ToString("ddMMyyyy") == formattedDate);

                foreach (var obj in filteredObjects)
                {
                    result.Add(new S3ImageInfo
                    {
                        Key = obj.Key,
                        LastModified = obj.LastModified
                    });
                }

                listRequest.ContinuationToken = listResponse.NextContinuationToken;

            } while (listResponse.IsTruncated);

            return result;
        }
    }

    // Helper class to store pre-signed URL with image info
    public class S3ImageInfo
    {
        public string Key { get; set; }
        public DateTime LastModified { get; set; }
        public string PreSignedUrl { get; set; }
    }
}
