using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Text;
using System.Text.Json;
using e_suite.API.Common;
using e_suite.API.Common.exceptions;
using e_suite.API.Common.models;
using e_suite.API.Common.repository;
using e_suite.Database.Audit;
using e_suite.Database.Core.Extensions;
using e_suite.Database.Core.Tables.CustomFields;
using e_suite.Service.CustomFieldValidation.Models;
using eSuite.Core.CustomFields;
using eSuite.Core.Miscellaneous;

namespace e_suite.Service.CustomFieldValidation;

public class CustomFieldValidator : ICustomFieldValidator
{
    private readonly ICustomFieldValidatorRepository _customFieldValidatorRepository;
    private readonly ISequenceManager _sequenceManager;

#pragma warning disable IDE0290
    public CustomFieldValidator(ICustomFieldValidatorRepository customFieldValidatorRepository, ISequenceManager sequenceManager)
    {
        _customFieldValidatorRepository = customFieldValidatorRepository;
        _sequenceManager = sequenceManager;
    }
#pragma warning restore IDE0290

    public async Task<IEnumerable<ValidatedCustomFieldValue>> ValidateFields(AuditUserDetails auditUserDetails, IEnumerable<CustomFieldValues> customFieldValues, IReadOnlyCollection<CustomField> customFields, CancellationToken cancellationToken)
    {
        var results = new List<ValidatedCustomFieldValue>() ??
                      throw new ArgumentNullException(nameof(customFields));

        var processedFields = new List<CustomField>();

        foreach (var customFieldValue in customFieldValues)
        { 
            var field = customFields.FindByGeneralIdRef(customFieldValue.Id) ??
                        throw new NullReferenceException("Custom field definition not found");

            processedFields.Add(field);

            var comparer = new CustomFieldValueComparer();
            var values = customFieldValue.Values.Distinct(comparer).Where( x => ValueIsPopulated(x.Value, field)).ToList();

            var isAutoGeneratedField = IsAutoGeneratedField(field);

            if (isAutoGeneratedField)
            {
                if (values.Count == 0)
                {
                    values.Add( new() {Value = string.Empty, DisplayValue = string.Empty });
                }
            }

            var entryCount = 0;
            for (var index = 0; index < values.Count; index++)
            {
                var value = await AssignValue(auditUserDetails, field, values[index], cancellationToken);

                entryCount++;
                results.Add(await CreateValidatedCustomFieldValue(field, index, value, cancellationToken));
            }
            
            CheckForEntryCountLimits(field, entryCount);
        }

        var missedFields = customFields.Except(processedFields).ToList();
        foreach (var field in missedFields)
        {
            var defaultValue = new CustomFieldValue
            {
                Value = field.DefaultValue,
                DisplayValue = field.DefaultValue
            };

            var value = await AssignValue(auditUserDetails, field, defaultValue, cancellationToken);

            var entryCount = 0;

            if (ValueIsPopulated(value, field))
            {
                entryCount++;
                results.Add(await CreateValidatedCustomFieldValue(field, 0, value, cancellationToken));
            }

            CheckForEntryCountLimits(field, entryCount);
        }

        ThrowIfValidationFailed(results);

        return results;
    }

    private static void ThrowIfValidationFailed(List<ValidatedCustomFieldValue> results)
    {
        var validationErrors = new StringBuilder();
        foreach (var result in results)
        {
            if (!result.Valid)
            {
                foreach (var error in result.ValidationErrors)
                {
                    validationErrors.AppendLine(result.CustomField.Name + ": " + error);
                }
            }
        }

        if (validationErrors.Length > 0)
            throw new ValidationException(validationErrors.ToString().Trim());
    }

    private static bool ValueIsPopulated(object value, CustomField field)
    {
        if (field.FieldType == FieldType.Text)
            return true;

        if (field.FieldType == FieldType.Number)
            return true;

        if (value is JsonElement { ValueKind: JsonValueKind.String } element)
        {
            return !string.IsNullOrWhiteSpace(element.ToString());
        }

        if (value is string valueAsString)
        {
            return (!string.IsNullOrWhiteSpace(valueAsString));
        }

        if (value != null)
        {
            return true;
        }

        return false;
    }

    private static bool IsAutoGeneratedField(CustomField field)
    {
        return field.FieldType switch
        {
            FieldType.Sequence => true,
            _ => false
        };
    }

    private static void CheckForEntryCountLimits(CustomField field, int entryCount)
    {
        if (field.MinEntries > entryCount)
            throw new MinimumRangeException($"{field.Name} Requires at least one entry");

        if (field.MaxEntries != null && field.MaxEntries < entryCount)
            throw new MaximumRangeException($"{field.Name} Can have {field.MaxEntries} entries at most");
    }

    private async Task<ValidatedCustomFieldValue> CreateValidatedCustomFieldValue(CustomField field, int index, object value, CancellationToken cancellationToken)
    {
        string cleanedValue = null!;
        string? displayValue = null!;

        var valueAsString = value.ToString();

        switch (field.FieldType)
        {
            case FieldType.FormTemplate:
                if (!string.IsNullOrWhiteSpace(valueAsString))
                {
                    var templateAsGeneralIdRef = ConvertValueToGeneralIdRef(value);
                    var template =
                        await _customFieldValidatorRepository.GetTemplateByGeneralRefId(templateAsGeneralIdRef!,
                            cancellationToken) ??
                        throw new NullReferenceException($"Unable to find template with Id {value}");

                    cleanedValue = template!.Guid.ToString();
                    displayValue = template.Name;
                }
                break;
            case FieldType.Glossary:
                if (!string.IsNullOrWhiteSpace(valueAsString))
                {
                    var valueAsGeneralIdRef = ConvertValueToGeneralIdRef(value);

                    var glossary =
                        await _customFieldValidatorRepository.GetGlossaryByGeneralRefId(valueAsGeneralIdRef!,
                            cancellationToken);
                    cleanedValue = glossary!.Guid.ToString();
                    displayValue = glossary.Name;
                }

                break;
            case FieldType.Domain:
                if (!string.IsNullOrWhiteSpace(valueAsString))
                {
                    var valueAsGeneralIdRef = ConvertValueToGeneralIdRef(value);

                    var domain =
                        await _customFieldValidatorRepository.GetDomainByGeneralRefId(valueAsGeneralIdRef!,
                            cancellationToken);
                    cleanedValue = domain!.Guid.ToString();
                    displayValue = domain.Name;
                }

                break;
            case FieldType.Number:
                cleanedValue = valueAsString!;
                displayValue = valueAsString;
                break;
            //case FieldType.Boolean:
            //case FieldType.Date:
            //case FieldType.Time:
            //case FieldType.DateTime:
            //case FieldType.Sequence:
            case FieldType.Text:
            default:
                cleanedValue = valueAsString!;
                displayValue = valueAsString;
                break;
}

        var validatedCustomFieldValue = new ValidatedCustomFieldValue
        {
            CustomField = field,
            CustomFieldId = field.Id,
            Index = index,
            Value = cleanedValue,
            DisplayValue = displayValue
        };

        await ValidateField(field, validatedCustomFieldValue, cancellationToken);

        return validatedCustomFieldValue;
    }

    private static readonly JsonSerializerOptions JsonSerializerOptions = new()
    {
        PropertyNameCaseInsensitive = true
    };


    private static GeneralIdRef ConvertValueToGeneralIdRef(object value)
    {

        GeneralIdRef valueAsGeneralIdRef;
        if (value is GeneralIdRef generalIdRef)
        {
            valueAsGeneralIdRef = generalIdRef;
        }
        else
        {
            valueAsGeneralIdRef = JsonSerializer.Deserialize<GeneralIdRef>(value.ToString()!, JsonSerializerOptions)!;
        }

        return valueAsGeneralIdRef;
    }

    private async Task<object> AssignValue(AuditUserDetails auditUserDetails, CustomField field, CustomFieldValue value, CancellationToken cancellationToken)
    {
        return field.FieldType switch
        {
            FieldType.Sequence => await GetNextSequenceValue(auditUserDetails, field, value, cancellationToken),
            _ => value.Value
        };
    }

    private async Task<string> GetNextSequenceValue(AuditUserDetails auditUserDetails, CustomField field, CustomFieldValue value, CancellationToken cancellationToken)
    {
        if (!string.IsNullOrWhiteSpace(value.Value.ToString()))
            return value.Value.ToString()!;

        var sequenceId = await _customFieldValidatorRepository.FindSequenceIdForCustomField(field.Id, cancellationToken);

        var sequenceValue = await _sequenceManager.NextValue(auditUserDetails, new GeneralIdRef { Id = sequenceId }, cancellationToken);
        return sequenceValue.Single();
    }

    private async Task ValidateField(CustomField field, ValidatedCustomFieldValue customFieldValue, CancellationToken cancellationToken)
    {
        switch (field.FieldType)
        {
            case FieldType.Number:
                var customFieldNumbers = await _customFieldValidatorRepository.GetCustomFieldNumbersAsync(field.Id, cancellationToken);

                if (string.IsNullOrWhiteSpace(customFieldValue.Value))
                {
                    if (field.MinEntries != 0)
                    {
                        customFieldValue.ValidationErrors.Add($"Missing required value");
                    }
                    break;
                }

                var numberValue = decimal.Parse(customFieldValue.Value);
                if (numberValue < customFieldNumbers.MinimumValue)
                    customFieldValue.ValidationErrors.Add($"{customFieldValue.Value} must be greater than or equal to {customFieldNumbers.MinimumValue}");
                if (numberValue > customFieldNumbers.MaximumValue)
                    customFieldValue.ValidationErrors.Add($"{customFieldValue.Value} must be less than or equal to {customFieldNumbers.MaximumValue}");

                customFieldValue.DisplayValue = numberValue.ToString(CultureInfo.InvariantCulture);
                customFieldValue.Value = numberValue.ToString(CultureInfo.InvariantCulture);
                break;
            //case FieldType.Boolean:
            //case FieldType.Date:
            //case FieldType.Time:
            //case FieldType.DateTime:
            case FieldType.Text:
                //todo Create text validation code.   Length, Regex match, Required, case sensitive
                break;
            case FieldType.Sequence:
                if (!ValueIsPopulated(customFieldValue.Value, field))
                    customFieldValue.ValidationErrors.Add($"{field.FieldType} Requires a value"); //Should not trigger in production.
                break;
            case FieldType.FormTemplate: //No need to specific validate for FormTemplates.  The value is already confirmed valid at this point.
            case FieldType.Glossary: //No need to specific validate for Glossary items.  The value is already confirmed valid at this point.
            case FieldType.Domain: //No need to specific validate for Glossary items.  The value is already confirmed valid at this point.
                break;
            default:
                customFieldValue.ValidationErrors.Add($"Fields of type {field.FieldType} are not currently supported"); //Should not trigger in production.
                break;
        }
    }
}