using System.Net;
using NPin.Framework.AuditLogging.Domain.Entities;
using NPin.Framework.AuditLogging.Domain.Events;
using NPin.Framework.AuditLogging.Domain.Repositories;
using NPin.Framework.SqlSugarCore.Abstractions;
using NPin.Framework.SqlSugarCore.Repositories;
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;

namespace NPin.Framework.AuditLogging.SqlSugarCore.Repositories;

public class AuditLogRepository : SqlSugarRepository<AuditLogAggregateRoot, Guid>, IAuditLogRepository
{
    public AuditLogRepository(ISugarDbContextProvider<ISqlSugarDbContext> sugarDbContextProvider) : base(
        sugarDbContextProvider)
    {
    }

    /// <summary>
    /// 重写插入逻辑以支持关联关系（导航）
    /// </summary>
    /// <param name="insertObj"></param>
    /// <returns></returns>
    public override async Task<bool> InsertAsync(AuditLogAggregateRoot insertObj)
    {
        return await Db.InsertNav(insertObj)
            .Include(z1 => z1.Actions)
            // .Include(z1=>z1.EntityChanges)
            // .ThenInclude(z2=>z2.PropertyChanges)
            .ExecuteCommandAsync();
    }


    public async Task<Dictionary<DateTime, double>> GetAverageExecutionDurationPerDayAsync(DateTime startDate,
        DateTime endDate,
        CancellationToken cancellationToken = default)
    {
        // 分组排序查询
        var result = await DbQueryable
            .Where(a => a.ExecutionTime < endDate.AddDays(1) && a.ExecutionTime > startDate)
            .OrderBy(t => t.ExecutionTime)
            .GroupBy(g => new { g.ExecutionTime!.Value.Date })
            .Select(s => new
            {
                Day = SqlFunc.AggregateMin(s.ExecutionTime),
                avgExecutionTime = SqlFunc.AggregateAvg(s.ExecutionDuration)
            })
            .ToListAsync(cancellationToken);

        // 散列为Dict
        return result.ToDictionary(e => e.Day!.Value.ClearTime(), e => (double)e.avgExecutionTime!);
    }

    public async Task<List<AuditLogAggregateRoot>> GetListAsync(string sorting = null, int maxResultCount = 50,
        int skipCount = 0, DateTime? startTime = null,
        DateTime? endTime = null, string httpMethod = null, string url = null, Guid? userId = null,
        string userName = null,
        string applicationName = null, string clientIpAddress = null, string correlationId = null,
        int? maxExecutionDuration = null, int? minExecutionDuration = null, bool? hasException = null,
        HttpStatusCode? httpStatusCode = null, bool includeDetails = false)
    {
        var query = await GetListQueryAsync(
            startTime,
            endTime,
            httpMethod,
            url,
            userId,
            userName,
            applicationName,
            clientIpAddress,
            correlationId,
            maxExecutionDuration,
            minExecutionDuration,
            hasException,
            httpStatusCode,
            includeDetails
        );
        var auditLogs = await query
            .OrderBy(sorting.IsNullOrWhiteSpace() ? $"{nameof(AuditLogAggregateRoot.ExecutionTime)} DESC" : sorting)
            .ToPageListAsync(skipCount, maxResultCount);
        return auditLogs;
    }

    public async Task<long> GetCountAsync(DateTime? startTime = null, DateTime? endTime = null,
        string httpMethod = null,
        string url = null,
        Guid? userId = null, string userName = null, string applicationName = null, string clientIpAddress = null,
        string correlationId = null, int? maxExecutionDuration = null, int? minExecutionDuration = null,
        bool? hasException = null, HttpStatusCode? httpStatusCode = null, CancellationToken cancellationToken = default)
    {
        var query = await GetListQueryAsync(
            startTime,
            endTime,
            httpMethod,
            url,
            userId,
            userName,
            applicationName,
            clientIpAddress,
            correlationId,
            maxExecutionDuration,
            minExecutionDuration,
            hasException,
            httpStatusCode
        );

        var totalCount = await query.CountAsync(cancellationToken);

        return totalCount;
    }

    protected virtual async Task<ISugarQueryable<AuditLogAggregateRoot>> GetListQueryAsync(
        DateTime? startTime = null,
        DateTime? endTime = null,
        string httpMethod = null,
        string url = null,
        Guid? userId = null,
        string userName = null,
        string applicationName = null,
        string clientIpAddress = null,
        string correlationId = null,
        int? maxExecutionDuration = null,
        int? minExecutionDuration = null,
        bool? hasException = null,
        HttpStatusCode? httpStatusCode = null,
        bool includeDetails = false)
    {
        var nHttpStatusCode = (int?)httpStatusCode;
        var queryable = DbQueryable
            .WhereIF(startTime.HasValue, auditLog => auditLog.ExecutionTime >= startTime)
            .WhereIF(endTime.HasValue, auditLog => auditLog.ExecutionTime <= endTime)
            .WhereIF(hasException.HasValue && hasException.Value,
                auditLog => auditLog.Exceptions != null && auditLog.Exceptions != "")
            .WhereIF(hasException.HasValue && !hasException.Value,
                auditLog => auditLog.Exceptions == null || auditLog.Exceptions == "")
            .WhereIF(httpMethod != null, auditLog => auditLog.HttpMethod == httpMethod)
            .WhereIF(url != null, auditLog => auditLog.Url != null && auditLog.Url.Contains(url))
            .WhereIF(userId != null, auditLog => auditLog.UserId == userId)
            .WhereIF(userName != null, auditLog => auditLog.UserName == userName)
            .WhereIF(applicationName != null, auditLog => auditLog.ApplicationName == applicationName)
            .WhereIF(clientIpAddress != null,
                auditLog => auditLog.ClientIpAddress != null && auditLog.ClientIpAddress == clientIpAddress)
            .WhereIF(correlationId != null, auditLog => auditLog.CorrelationId == correlationId)
            .WhereIF(httpStatusCode != null && httpStatusCode > 0,
                auditLog => auditLog.HttpStatusCode == nHttpStatusCode)
            .WhereIF(maxExecutionDuration != null && maxExecutionDuration.Value > 0,
                auditLog => auditLog.ExecutionDuration <= maxExecutionDuration)
            .WhereIF(minExecutionDuration != null && minExecutionDuration.Value > 0,
                auditLog => auditLog.ExecutionDuration >= minExecutionDuration);
        
        return queryable;
    }

    public async Task<EntityChangeEntity> GetEntityChange(Guid entityChangeId,
        CancellationToken cancellationToken = default)
    {
        var entityChange = await (await GetDbContextAsync()).Queryable<EntityChangeEntity>()
            .Where(x => x.Id == entityChangeId)
            .OrderBy(x => x.Id)
            .FirstAsync(cancellationToken);
        if (entityChange == null)
        {
            throw new EntityNotFoundException(typeof(EntityChangeEntity));
        }

        return entityChange;
    }

    public async Task<long> GetEntityChangeCountAsync(Guid? auditLogId = null, DateTime? startTime = null,
        DateTime? endTime = null,
        EntityChangeType? changeType = null, string entityId = null, string entityTypeFullName = null,
        CancellationToken cancellationToken = default)
    {
        var query = await GetEntityChangeListQueryAsync(auditLogId, startTime, endTime, changeType, entityId,
            entityTypeFullName);
        var totalCount = await query.CountAsync(cancellationToken);
        return totalCount;
    }

    public async Task<List<EntityChangeEntity>> GetEntityChangeListAsync(string sorting = null, int maxResultCount = 50,
        int skipCount = 0,
        Guid? auditLogId = null, DateTime? startTime = null, DateTime? endTime = null,
        EntityChangeType? changeType = null,
        string entityId = null, string entityTypeFullName = null, bool includeDetails = false,
        CancellationToken cancellationToken = default)
    {
        var query = await GetEntityChangeListQueryAsync(auditLogId, startTime, endTime, changeType, entityId,
            entityTypeFullName, includeDetails);
        return await query
            .OrderBy(sorting.IsNullOrWhiteSpace() ? $"{nameof(EntityChangeEntity.ChangeTime)} DESC" : sorting)
            .ToPageListAsync(skipCount, maxResultCount, cancellationToken);
    }

    protected virtual async Task<ISugarQueryable<EntityChangeEntity>> GetEntityChangeListQueryAsync(
        Guid? auditLogId = null,
        DateTime? startTime = null,
        DateTime? endTime = null,
        EntityChangeType? changeType = null,
        string entityId = null,
        string entityTypeFullName = null,
        bool includeDetails = false)
    {
        return (await GetDbContextAsync())
            .Queryable<EntityChangeEntity>()
            .WhereIF(auditLogId.HasValue, e => e.AuditLogId == auditLogId)
            .WhereIF(startTime.HasValue, e => e.ChangeTime >= startTime)
            .WhereIF(endTime.HasValue, e => e.ChangeTime <= endTime)
            .WhereIF(changeType.HasValue, e => e.ChangeType == changeType)
            .WhereIF(!string.IsNullOrWhiteSpace(entityId), e => e.EntityId == entityId)
            .WhereIF(!string.IsNullOrWhiteSpace(entityTypeFullName),
                e => e.EntityTypeFullName!.Contains(entityTypeFullName));
    }

    public async Task<List<EntityChangeWithUsername>> GetEntityChangesWithUsernameAsync(string entityId,
        string entityTypeFullName,
        CancellationToken cancellationToken = default)
    {
        var query = (await GetDbContextAsync()).Queryable<EntityChangeEntity>()
            .Where(x => x.EntityId == entityId && x.EntityTypeFullName == entityTypeFullName);

        var result = await query.LeftJoin<AuditLogAggregateRoot>((x, audit) => x.AuditLogId == audit.Id)
            .Select((x, audit) => new EntityChangeWithUsername { EntityChange = x, Username = audit.UserName! })
            .OrderByDescending(x => x.EntityChange.ChangeTime)
            .ToListAsync(cancellationToken);
        return result;
    }

    public async Task<EntityChangeWithUsername> GetEntityChangeWithUsernameAsync(Guid entityChangeId)
    {
        var auditLog = await DbQueryable
            .Where(x => x.EntityChanges.Any(y => y.Id == entityChangeId))
            .FirstAsync();
        return new EntityChangeWithUsername
        {
            EntityChange = auditLog.EntityChanges.First(x => x.Id == entityChangeId),
            Username = auditLog.UserName!
        };
    }
}