using FluentValidation;
using IWS5.Core.DTO;
using IWS5.Core.Enums;
using IWS5.Core.Models;
using IWS5.Core.Models.Request;
using IWS5.Core.Services;
using IWS5.DatabaseAccess.Interfaces;
using Microsoft.Data.SqlClient;
using Serilog;

namespace IWS5.Services;

public class FolderFieldService(
    IWorkItemReferenceRepository workItemReferenceRepository,
    IRightsRepository rightsRepository,
    IFieldDefinitionRepository fieldDefinitionRepository,
    IValidator<UpdateFieldListOfValuesRequest> updateFieldListOfValuesRequestValidator,
    ILogger logger)
    : IFolderFieldService
{
    public async Task<CodeResponse<FolderFieldListDTO>> ListFolderFieldItems(FolderFieldRequest folderFieldRequest)
    {
        logger.Information("Starting ListFolderFieldItems for User {UserName}", folderFieldRequest.UserName);

        if (folderFieldRequest.WorkItemReferenceId == 0)
        {
            const string message = "WorkItemReferenceId cannot be 0";
            logger.Error(message);
            return new CodeResponse<FolderFieldListDTO>(ResponseCode.BadRequest, message);
        }
        bool isLocked = true;
        try
        {
            isLocked =
            await workItemReferenceRepository.IsLockedForUserByWorkItemReferenceId(
                folderFieldRequest.WorkItemReferenceId, folderFieldRequest.UserName);
        }
        catch (Exception ex)
        {
            logger.Error(ex.Message);
            return new CodeResponse<FolderFieldListDTO>(ResponseCode.NotFound, ex.Message);
        }

        if (isLocked)
        {
            var message = $"User {folderFieldRequest.UserName} is not allowed to view field values for WorkItemReferenceId {folderFieldRequest.WorkItemReferenceId} because it is locked";
            logger.Error(message);
            return new CodeResponse<FolderFieldListDTO>(ResponseCode.Forbidden, message);
        }

        var newDataResponse = await fieldDefinitionRepository
                .ListFieldDefinitionsAndInstancesByWorkItemReferenceIdAsync(folderFieldRequest.WorkItemReferenceId, folderFieldRequest.UserName);

        var rights = await rightsRepository.ListUserRelatedRights(folderFieldRequest.UserName, ObjectType.FolderField, ATRightAllowRight.ViewFolderField, ATRightAllowRight.EditFolderField);
        
        var allowedToEditFields = rights.Where(right => right.AllowRights == (int)ATRightAllowRight.EditFolderField)
            .Select(right => right.ObjectGuid).ToHashSet();

        var fieldList = await Task.WhenAll(newDataResponse.Select(async fDefinition =>
        {
            var fieldInstanceId = fDefinition.DataType switch
            {
                (int)FieldInstanceType.Date => fDefinition.AtfieldInstanceDates.FirstOrDefault()
                    ?.FieldInstanceId,
                (int)FieldInstanceType.Number => fDefinition.AtfieldInstanceNumbers.FirstOrDefault()
                    ?.FieldInstanceId,
                (int)FieldInstanceType.String => fDefinition.AtfieldInstanceStrings.FirstOrDefault()
                    ?.FieldInstanceId,
                (int)FieldInstanceType.Option => fDefinition.AtfieldInstanceOptions.FirstOrDefault()
                    ?.FieldInstanceId,
                _ => null
            };

            var fieldInstanceValue = fDefinition.DataType switch
            {
                (int)FieldInstanceType.Date => fDefinition.AtfieldInstanceDates.FirstOrDefault()?.FieldValue
                    .ToString(),
                (int)FieldInstanceType.Number => fDefinition.AtfieldInstanceNumbers.FirstOrDefault()?.FieldValue
                    .ToString(),
                (int)FieldInstanceType.String => fDefinition.AtfieldInstanceStrings.FirstOrDefault()?.FieldValue
                    .ToString(),
                (int)FieldInstanceType.Option => fDefinition.AtfieldInstanceOptions.FirstOrDefault()?.FieldValue
                    .ToString(),
                _ => null
            };

            List<string>? fieldOptions = null;
            if (fDefinition.DataType == (int)FieldInstanceType.Option)
            {
                fieldOptions = await fieldDefinitionRepository.ListFieldOptionsByInitialValueSqlAsync(fDefinition.InitialValue);
            }

            return new FolderFieldItem
            {
                FieldId = fDefinition.FieldId,
                FieldProcessId = fDefinition.ProcessId,
                FieldInstanceId = fieldInstanceId,
                FieldName = fDefinition.FieldName,
                InstanceType = (FieldInstanceType)fDefinition.DataType,
                FieldValue = fieldInstanceValue,
                DataType = fDefinition.DataType,
                Index = fDefinition.Index,
                Alias = fDefinition.Alias,
                InitialValue = fDefinition.DataType != (int)FieldInstanceType.Option ? fDefinition.InitialValue : null,
                FieldOptions = fieldOptions,
                EditAllowed = allowedToEditFields.Contains(fDefinition.ObjectId)
            };
        }));

        var newDataResult = new FolderFieldListDTO
        {
            WorkItemReferenceId = folderFieldRequest.WorkItemReferenceId,
            FieldList = fieldList
        };

        logger.Information("Successfully retrieved {Count} folder field items for User {UserName}",
            newDataResult.FieldList.Length, folderFieldRequest.UserName);
        return new CodeResponse<FolderFieldListDTO>(ResponseCode.OK, "", newDataResult);
    }

    public async Task<CodeResponse<List<FolderFieldItem>>> ListFolderFieldItems(FolderFieldsByProcessIdRequest request)
    {
        logger.Information("Starting ListFolderFieldItems for User {UserName} with ProcessId {ProcessId}", request.UserName, request.ProcessId);

        var rights = await rightsRepository.ListUserRelatedRights(request.UserName, ObjectType.FolderField, ATRightAllowRight.ViewFolderField, ATRightAllowRight.EditFolderField);
        logger.Information("Retrieved {Count} rights for User {UserName}", rights.Count, request.UserName);
        
        var allowedToViewFields = rights.Where(right => right.AllowRights == (int)ATRightAllowRight.ViewFolderField)
            .Select(right => right.ObjectGuid).ToHashSet();
        var allowedToEditFields = rights.Where(right => right.AllowRights == (int)ATRightAllowRight.EditFolderField)
            .Select(right => right.ObjectGuid).ToHashSet();
        
        var fields = await fieldDefinitionRepository.ListAsync(fieldDefinition => fieldDefinition.ProcessId == request.ProcessId && fieldDefinition.Display);
        logger.Information("Retrieved {Count} fields for ProcessId {ProcessId}", fields.Count, request.ProcessId);

        var fieldOptions = new Dictionary<int, List<string>>();
        foreach (var field in fields.Where(def => def.DataType == (int)FieldInstanceType.Option))
        {
            var optionsList = await fieldDefinitionRepository.ListFieldOptionsByInitialValueSqlAsync(field.InitialValue);
            fieldOptions.Add(field.FieldId, optionsList);
        }

        var result = fields.Where(fDefinition => allowedToViewFields.Contains(fDefinition.ObjectId))
            .Select(fieldDef => new FolderFieldItem
        {
            FieldId = fieldDef.FieldId,
            FieldInstanceId = 0,
            FieldProcessId = fieldDef.ProcessId,
            DataType = fieldDef.DataType,
            InstanceType = (FieldInstanceType)fieldDef.DataType,
            FieldName = fieldDef.FieldName,
            FieldValue = string.Empty,
            Index = fieldDef.Index,
            Alias = fieldDef.Alias,
            InitialValue = fieldDef.DataType != (int)FieldInstanceType.Option ? fieldDef.InitialValue : null,
            FieldOptions = fieldDef.DataType == (int)FieldInstanceType.Option ? fieldOptions[fieldDef.FieldId] : null,
            EditAllowed = allowedToEditFields.Contains(fieldDef.ObjectId)
        }).OrderBy(fields=> fields.Index ?? fields.FieldId).ToList();

        logger.Information("Successfully retrieved {Count} folder field items for User {UserName}", result.Count, request.UserName);
        return new CodeResponse<List<FolderFieldItem>>(ResponseCode.OK,"", result);
    }

    public async Task<CodeResponse<String>> UpdateFieldListOfValues(string userName, UpdateFieldListOfValuesRequest updateFieldListOfValuesRequest)
    {
        var isLocked =
            await workItemReferenceRepository.IsLockedForUserByWorkItemId(updateFieldListOfValuesRequest.WorkItemId,
                userName);

        if (isLocked)
        {
            var message = $"User {userName} is not allowed to edit field values for WorkItemId {updateFieldListOfValuesRequest.WorkItemId} because it is locked";
            logger.Error(message);
            return new CodeResponse<string>(ResponseCode.Forbidden, null, message);
        }

        await updateFieldListOfValuesRequestValidator.ValidateAndThrowAsync(updateFieldListOfValuesRequest);

        var dateFields = new List<UpdateFolderFieldValueRequest>();
        var numberFields = new List<UpdateFolderFieldValueRequest>();
        var stringFields = new List<UpdateFolderFieldValueRequest>();
        var optionFields = new List<UpdateFolderFieldValueRequest>();
        var fieldIds = updateFieldListOfValuesRequest.UpdatedFields.Select(f => f.FieldId).ToArray();
        
        var fieldDefinitions =
            await fieldDefinitionRepository.ListAsync(fieldDefinition => fieldIds.Contains(fieldDefinition.FieldId));

        var rights = await rightsRepository
            .ListUserRelatedRights(userName, ObjectType.FolderField, ATRightAllowRight.EditFolderField,
                fieldDefinitions.Select(f => f.ObjectId).ToArray());
        
        var rightIds = rights.Select(right => right.ObjectGuid).Distinct().ToArray();
        
        var fieldDefinitionsWithoutRights = fieldDefinitions
            .Where(fieldDefinition => !rightIds.Contains(fieldDefinition.ObjectId))
            .ToList();
        
        if (fieldDefinitionsWithoutRights.Any())
        {
            var message = $"User {userName} does not have rights to update the following field definitions: {string.Join(", ", fieldDefinitionsWithoutRights.Select(f => f.FieldId))}";
            logger.Error(message);
            return new CodeResponse<string>(ResponseCode.Forbidden, null, message);
        }

        foreach (var field in updateFieldListOfValuesRequest.UpdatedFields)
        {
            switch (field.InstanceType)
            {
                case FieldInstanceType.Date:
                    dateFields.Add(field);
                    break;
                case FieldInstanceType.Number:
                    numberFields.Add(field);
                    break;
                case FieldInstanceType.String:
                    stringFields.Add(field);
                    break;
                case FieldInstanceType.Option:
                    optionFields.Add(field);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        var updatedFieldsAmount = await fieldDefinitionRepository.UpdateFieldListOfValues(dateFields, numberFields, stringFields, optionFields,
            updateFieldListOfValuesRequest.WorkItemId);
        
        var successMessage = $"Successfully updated {updatedFieldsAmount} field values for WorkItemId {updateFieldListOfValuesRequest.WorkItemId}";
        logger.Information(successMessage);
        return new CodeResponse<string>(ResponseCode.OK, null, successMessage);
    }
}