using Fengling.Console.Managers;
using Fengling.Console.Models.Dtos;
using Fengling.Console.Repositories;
using Microsoft.AspNetCore.Identity;
using System.Security.Claims;
using Fengling.Console.Datas;
using Fengling.Console.Models.Entities;
using TenantStatus = Fengling.Console.Models.Entities.TenantStatus;

namespace Fengling.Console.Services;

public interface ITenantService
{
    Task<(IEnumerable<TenantDto> Items, int TotalCount)> GetTenantsAsync(int page, int pageSize, string? name = null,
        string? tenantCode = null, TenantStatus? status = null);
    Task<TenantDto?> GetTenantAsync(int id);
    Task<IEnumerable<UserDto>> GetTenantUsersAsync(int tenantId);
    Task<IEnumerable<object>> GetTenantRolesAsync(int tenantId);
    Task<TenantSettingsDto> GetTenantSettingsAsync(int id);
    Task UpdateTenantSettingsAsync(int id, TenantSettingsDto settings);
    Task<TenantDto> CreateTenantAsync(CreateTenantDto dto);
    Task<TenantDto> UpdateTenantAsync(int id, UpdateTenantDto dto);
    Task DeleteTenantAsync(int id);
}

public class TenantService(
    ITenantManager tenantManager,
    IUserRepository userRepository,
    IRoleRepository roleRepository,
    UserManager<ApplicationUser> userManager,
    ApplicationDbContext context,
    IHttpContextAccessor httpContextAccessor)
    : ITenantService
{
    public async Task<(IEnumerable<TenantDto> Items, int TotalCount)> GetTenantsAsync
        (int page, int pageSize, string? name = null, string? tenantCode = null, TenantStatus? status = null)
    {
        var tenants = await tenantManager.GetPagedAsync(page, pageSize, name, tenantCode, status);
        var totalCount = await tenantManager.GetCountAsync(name, tenantCode, status);

        var tenantDtos = new List<TenantDto>();
        foreach (var tenant in tenants)
        {
            var userCount = await tenantManager.GetUserCountAsync(tenant.Id);
            tenantDtos.Add(new TenantDto
            {
                Id = tenant.Id,
                TenantCode = tenant.TenantCode,
                Name = tenant.Name,
                ContactName = tenant.ContactName,
                ContactEmail = tenant.ContactEmail,
                ContactPhone = tenant.ContactPhone,
                MaxUsers = tenant.MaxUsers,
                UserCount = userCount,
                Status = tenant.Status,
                ExpiresAt = tenant.ExpiresAt,
                Description = tenant.Description,
                CreatedAt = tenant.CreatedAt
            });
        }

        return (tenantDtos, totalCount);
    }

    public async Task<TenantDto?> GetTenantAsync(int id)
    {
        var tenant = await tenantManager.FindByIdAsync(id);
        if (tenant == null) return null;

        return new TenantDto
        {
            Id = tenant.Id,
            TenantCode = tenant.TenantCode,
            Name = tenant.Name,
            ContactName = tenant.ContactName,
            ContactEmail = tenant.ContactEmail,
            ContactPhone = tenant.ContactPhone,
            MaxUsers = tenant.MaxUsers,
            UserCount = await tenantManager.GetUserCountAsync(tenant.Id),
            Status = tenant.Status,
            ExpiresAt = tenant.ExpiresAt,
            Description = tenant.Description,
            CreatedAt = tenant.CreatedAt
        };
    }

    public async Task<IEnumerable<UserDto>> GetTenantUsersAsync(int tenantId)
    {
        var tenant = await tenantManager.FindByIdAsync(tenantId);
        if (tenant == null)
        {
            throw new KeyNotFoundException($"Tenant with ID {tenantId} not found");
        }

        var users = await userRepository.GetPagedAsync(1, int.MaxValue, null, null, tenantId.ToString());
        var userDtos = new List<UserDto>();

        foreach (var user in users)
        {
            var roles = await userManager.GetRolesAsync(user);
            userDtos.Add(new UserDto
            {
                Id = user.Id,
                UserName = user.UserName,
                Email = user.Email,
                RealName = user.RealName,
                TenantCode = user.TenantId.ToString(),
                TenantName = tenant?.Name ?? "",
                Roles = roles.ToList(),
                EmailConfirmed = user.EmailConfirmed,
                IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,
                CreatedAt = user.CreatedTime
            });
        }

        return userDtos;
    }

    public async Task<IEnumerable<object>> GetTenantRolesAsync(int tenantId)
    {
        var tenant = await tenantManager.FindByIdAsync(tenantId);
        if (tenant == null)
        {
            throw new KeyNotFoundException($"Tenant with ID {tenantId} not found");
        }

        var roles = await roleRepository.GetPagedAsync(1, int.MaxValue, null, tenantId.ToString());
        return roles.Select(r => new
        {
            id = r.Id,
            name = r.Name,
            displayName = r.DisplayName
        });
    }

    public async Task<TenantSettingsDto> GetTenantSettingsAsync(int id)
    {
        var tenant = await tenantManager.FindByIdAsync(id);
        if (tenant == null)
        {
            throw new KeyNotFoundException($"Tenant with ID {id} not found");
        }

        return new TenantSettingsDto
        {
            AllowRegistration = false,
            AllowedEmailDomains = "",
            DefaultRoleId = null,
            PasswordPolicy = new List<string> { "requireNumber", "requireLowercase" },
            MinPasswordLength = 8,
            SessionTimeout = 120
        };
    }

    public async Task UpdateTenantSettingsAsync(int id, TenantSettingsDto settings)
    {
        var tenant = await tenantManager.FindByIdAsync(id);
        if (tenant == null)
        {
            throw new KeyNotFoundException($"Tenant with ID {id} not found");
        }

        await CreateAuditLog("tenant", "update", "TenantSettings", tenant.Id, tenant.Name, null, System.Text.Json.JsonSerializer.Serialize(settings));
    }

    public async Task<TenantDto> CreateTenantAsync(CreateTenantDto dto)
    {
        var tenant = new Tenant
        {
            TenantCode = dto.TenantCode,
            Name = dto.Name,
            ContactName = dto.ContactName,
            ContactEmail = dto.ContactEmail,
            ContactPhone = dto.ContactPhone,
            MaxUsers = dto.MaxUsers,
            Description = dto.Description,
            ExpiresAt = dto.ExpiresAt,
            Status = TenantStatus.Active,
            CreatedAt = DateTime.UtcNow
        };

        var result = await tenantManager.CreateAsync(tenant);

        if (!result.Succeeded)
        {
            var errors = string.Join(", ", result.Errors.Select(e => e.Description));
            throw new InvalidOperationException($"Failed to create tenant: {errors}");
        }

        await CreateAuditLog("tenant", "create", "Tenant", tenant.Id, tenant.Name, null, 
            System.Text.Json.JsonSerializer.Serialize(dto));

        return new TenantDto
        {
            Id = tenant.Id,
            TenantCode = tenant.TenantCode,
            Name = tenant.Name,
            ContactName = tenant.ContactName,
            ContactEmail = tenant.ContactEmail,
            ContactPhone = tenant.ContactPhone,
            MaxUsers = tenant.MaxUsers,
            UserCount = 0,
            Status = tenant.Status,
            ExpiresAt = tenant.ExpiresAt,
            Description = tenant.Description,
            CreatedAt = tenant.CreatedAt
        };
    }

    public async Task<TenantDto> UpdateTenantAsync(int id, UpdateTenantDto dto)
    {
        var tenant = await tenantManager.FindByIdAsync(id);
        if (tenant == null)
        {
            throw new KeyNotFoundException($"Tenant with ID {id} not found");
        }

        var oldValue = System.Text.Json.JsonSerializer.Serialize(tenant);

        tenant.Name = dto.Name;
        tenant.ContactName = dto.ContactName;
        tenant.ContactEmail = dto.ContactEmail;
        tenant.ContactPhone = dto.ContactPhone;
        tenant.UpdatedAt = DateTime.UtcNow;

        var result = await tenantManager.UpdateAsync(tenant);

        if (!result.Succeeded)
        {
            var errors = string.Join(", ", result.Errors.Select(e => e.Description));
            throw new InvalidOperationException($"Failed to update tenant: {errors}");
        }

        await CreateAuditLog("tenant", "update", "Tenant", tenant.Id, tenant.Name, oldValue, 
            System.Text.Json.JsonSerializer.Serialize(tenant));

        return new TenantDto
        {
            Id = tenant.Id,
            TenantCode = tenant.TenantCode,
            Name = tenant.Name,
            ContactName = tenant.ContactName,
            ContactEmail = tenant.ContactEmail,
            ContactPhone = tenant.ContactPhone,
            MaxUsers = tenant.MaxUsers,
            UserCount = await tenantManager.GetUserCountAsync(tenant.Id),
            Status = tenant.Status,
            ExpiresAt = tenant.ExpiresAt,
            Description = tenant.Description,
            CreatedAt = tenant.CreatedAt
        };
    }

    public async Task DeleteTenantAsync(int id)
    {
        var tenant = await tenantManager.FindByIdAsync(id);
        if (tenant == null)
        {
            throw new KeyNotFoundException($"Tenant with ID {id} not found");
        }

        var oldValue = System.Text.Json.JsonSerializer.Serialize(tenant);

        var users = await userRepository.GetPagedAsync(1, int.MaxValue, null, null, id.ToString());
        foreach (var user in users)
        {
            user.IsDeleted = true;
            user.UpdatedTime = DateTime.UtcNow;
            await context.SaveChangesAsync();
        }

        tenant.IsDeleted = true;
        var result = await tenantManager.UpdateAsync(tenant);

        if (!result.Succeeded)
        {
            var errors = string.Join(", ", result.Errors.Select(e => e.Description));
            throw new InvalidOperationException($"Failed to delete tenant: {errors}");
        }

        await CreateAuditLog("tenant", "delete", "Tenant", tenant.Id, tenant.Name, oldValue);
    }

    private async Task CreateAuditLog(string operation, string action, string targetType, int? targetId, string? targetName, string? oldValue = null, string? newValue = null)
    {
        var httpContext = httpContextAccessor.HttpContext;
        var userName = httpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier) ?? httpContext?.User?.Identity?.Name ?? "system";
        var tenantIdClaim = httpContext?.User?.FindFirstValue("TenantId");

        var log = new AuditLog
        {
            Operator = userName,
            TenantId = tenantIdClaim,
            Operation = operation,
            Action = action,
            TargetType = targetType,
            TargetId = targetId.HasValue ? (long)targetId.Value : null,
            TargetName = targetName,
            IpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString() ?? "unknown",
            Status = "success",
            OldValue = oldValue,
            NewValue = newValue,
            CreatedAt = DateTime.UtcNow
        };

        context.AuditLogs.Add(log);
        await context.SaveChangesAsync();
    }
}
