using System.ComponentModel;
using System.Linq.Expressions;
using System.Text.Json;
using e_suite.API.Common;
using e_suite.API.Common.models;
using e_suite.API.Common.repository;
using e_suite.Database.Audit.Tables.Audit;
using e_suite.Utilities.Pagination;

namespace e_suite.Modules.AuditLog;

public class AuditLog : IAuditLog
{
    private readonly IAuditLogRepository _auditLogRepository;

    public AuditLog(IAuditLogRepository auditLogRepository)
    {
        _auditLogRepository = auditLogRepository;
    }

    public async Task<IPaginatedData<AuditLogEntry>> GetAuditLogEntries(Paging paging, string? logEntry, bool primaryOnly, CancellationToken cancellationToken)
    {
        AuditParams? auditParams = null;
        if (logEntry != null)
        {
            var buffer = Convert.FromBase64String(logEntry);
            auditParams = JsonSerializer.Deserialize<AuditParams>(buffer);
        }

        var auditEntries = _auditLogRepository.GetAuditEntries(auditParams, primaryOnly);

        var paginatedData =
            await PaginatedData.Paginate(auditEntries, paging, KeySelector, FilterSelector, cancellationToken);
        
        var paginatedResult = new PaginatedData<AuditLogEntry>
        {
            Count = paginatedData.Count,
            Page = paginatedData.Page,
            PageSize = paginatedData.PageSize,
            Data = paginatedData.Data.Select(x => new AuditLogEntry
            {
                Id = x.Id,
                UserId = x.AuditLog.UserId,
                UserDisplayName = x.AuditLog.UserDisplayName,
                Type = x.AuditLog.Type,
                DateTime = x.AuditLog.DateTime,
                Fields = x.AuditLog.Fields,
                Comment = x.AuditLog.Comment,
                EntityName = x.EntityName,
                EntityDisplayName = GetDisplayNameForClass( x.EntityName ),
                PrimaryKey = x.PrimaryKey,
                DisplayName  = x.DisplayName ?? string.Empty
            })
        };

        return paginatedResult;
    }

    private string GetDisplayNameForClass(string entityName)
    {
        var type = GetTypeFromFullName(entityName);

        if (type != null)
        {
            var displayName = GetDisplayName(type);
            if (displayName != null)
                return displayName;

            return type.Name;
        }

        return entityName;
    }

    private static string? GetDisplayName(Type type)
    {
        return (from attribute in type.CustomAttributes where attribute.AttributeType == typeof(DisplayNameAttribute) select attribute.ConstructorArguments[0].ToString().Trim('"')).FirstOrDefault();
    }

    private readonly Dictionary<string, Type> cacheDictionary = new();

    private Type? GetTypeFromFullName(string className)
    {
        if (cacheDictionary.TryGetValue(className, out var name))
            return name;

        foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            foreach (var type in assembly.GetTypes())
            {
                if (type.FullName == null ||
                    !type.FullName.Equals(className, StringComparison.OrdinalIgnoreCase))
                    continue;

                cacheDictionary.Add(className, type);
                return type;
            }
        }

        return null;
    }

    private Expression<Func<AuditEntry, bool>> FilterSelector(string? key, string value)
    {
        return key?.ToLowerInvariant() switch
        {
            "id" => x => x.Id.ToString().Contains(value),
            "userdisplayname" => x => x.AuditLog.UserDisplayName.Contains(value),
            "type" => x => x.AuditLog.Type.Contains(value),
            "comment" => x => x.AuditLog.Comment.Contains(value),
            "displayname" => x => x.DisplayName!.Contains(value),
            _ => x => true
        };

    }

    private Expression<Func<AuditEntry, object>> KeySelector(string? sortKey)
    {
        return sortKey?.ToLowerInvariant() switch
        {
            "id" => x => x.Id,
            "userid" => x => x.AuditLog.UserId,
            "userdisplayname" => x => x.AuditLog.UserDisplayName,
            "type" => x => x.AuditLog.Type,
            "datetime" => x => x.AuditLog.DateTime,
            "fields" => x => x.AuditLog.Fields,
            "comment" => x => x.AuditLog.Comment,
            "entityname" => x => x.EntityName,
            "primarykey" => x => x.PrimaryKey,
            "displayname" => x => x.DisplayName!,
            _ => x => x.AuditLog.DateTime
        };
    }
}