using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using YarpGateway.Data;
using YarpGateway.Models;
using Fengling.Console.Models.Dtos;

namespace Fengling.Console.Services;

public interface IGatewayService
{
    Task<GatewayStatisticsDto> GetStatisticsAsync();
    Task<List<GatewayServiceDto>> GetServicesAsync(bool globalOnly = false, string? tenantCode = null);
    Task<GatewayServiceDto?> GetServiceAsync(string serviceName, string? tenantCode = null);
    Task<GatewayServiceDto> RegisterServiceAsync(CreateGatewayServiceDto dto);
    Task<bool> UnregisterServiceAsync(string serviceName, string? tenantCode = null);
    Task<List<GatewayRouteDto>> GetRoutesAsync(bool globalOnly = false);
    Task<GatewayRouteDto> CreateRouteAsync(CreateGatewayRouteDto dto);
    Task<List<GatewayInstanceDto>> GetInstancesAsync(string clusterId);
    Task<GatewayInstanceDto> AddInstanceAsync(CreateGatewayInstanceDto dto);
    Task<bool> RemoveInstanceAsync(long instanceId);
    Task<bool> UpdateInstanceWeightAsync(long instanceId, int weight);
    Task ReloadGatewayAsync();
}

public class GatewayService : IGatewayService
{
    private readonly GatewayDbContext _dbContext;
    private readonly ILogger<GatewayService> _logger;

    public GatewayService(GatewayDbContext dbContext, ILogger<GatewayService> logger)
    {
        _dbContext = dbContext;
        _logger = logger;
    }

    public async Task<GatewayStatisticsDto> GetStatisticsAsync()
    {
        var routes = await _dbContext.TenantRoutes.Where(r => !r.IsDeleted).ToListAsync();
        var instances = await _dbContext.ServiceInstances.Where(i => !i.IsDeleted).ToListAsync();

        return new GatewayStatisticsDto
        {
            TotalServices = routes.Select(r => r.ServiceName).Distinct().Count(),
            GlobalRoutes = routes.Count(r => r.IsGlobal),
            TenantRoutes = routes.Count(r => !r.IsGlobal),
            TotalInstances = instances.Count,
            HealthyInstances = instances.Count(i => i.Health == 1),
            RecentServices = routes
                .OrderByDescending(r => r.CreatedTime)
                .Take(5)
                .Select(MapToServiceDto)
                .ToList()
        };
    }

    public async Task<List<GatewayServiceDto>> GetServicesAsync(bool globalOnly = false, string? tenantCode = null)
    {
        var query = _dbContext.TenantRoutes.Where(r => !r.IsDeleted);

        if (globalOnly)
            query = query.Where(r => r.IsGlobal);
        else if (!string.IsNullOrEmpty(tenantCode))
            query = query.Where(r => r.TenantCode == tenantCode);

        var routes = await query.OrderByDescending(r => r.CreatedTime).ToListAsync();
        var clusters = routes.Select(r => r.ClusterId).Distinct().ToList();

        var instances = await _dbContext.ServiceInstances
            .Where(i => clusters.Contains(i.ClusterId) && !i.IsDeleted)
            .GroupBy(i => i.ClusterId)
            .ToDictionaryAsync(g => g.Key, g => g.Count());

        return routes.Select(r => MapToServiceDto(r, instances.GetValueOrDefault(r.ClusterId, 0))).ToList();
    }

    public async Task<GatewayServiceDto?> GetServiceAsync(string serviceName, string? tenantCode = null)
    {
        var route = await _dbContext.TenantRoutes
            .FirstOrDefaultAsync(r =>
                r.ServiceName == serviceName &&
                r.IsDeleted == false &&
                (r.IsGlobal || r.TenantCode == tenantCode));

        if (route == null) return null;

        var instances = await _dbContext.ServiceInstances
            .CountAsync(i => i.ClusterId == route.ClusterId && !i.IsDeleted);

        return MapToServiceDto(route, instances);
    }

    public async Task<GatewayServiceDto> RegisterServiceAsync(CreateGatewayServiceDto dto)
    {
        var clusterId = $"{dto.ServicePrefix}-service";
        var pathPattern = $"/{dto.ServicePrefix}/{dto.Version}/{{**path}}";
        var destinationId = string.IsNullOrEmpty(dto.DestinationId)
            ? $"{dto.ServicePrefix}-1"
            : dto.DestinationId;

        // Check if route already exists
        var existingRoute = await _dbContext.TenantRoutes
            .FirstOrDefaultAsync(r =>
                r.ServiceName == dto.ServicePrefix &&
                r.IsGlobal == dto.IsGlobal &&
                (dto.IsGlobal || r.TenantCode == dto.TenantCode));

        if (existingRoute != null)
        {
            throw new InvalidOperationException($"Service {dto.ServicePrefix} already registered");
        }

        // Add instance
        var instanceId = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
        var instance = new GwServiceInstance
        {
            Id = instanceId,
            ClusterId = clusterId,
            DestinationId = destinationId,
            Address = dto.ServiceAddress,
            Weight = dto.Weight,
            Health = 1,
            Status = 1,
            CreatedTime = DateTime.UtcNow
        };
        await _dbContext.ServiceInstances.AddAsync(instance);

        // Add route
        var routeId = instanceId + 1;
        var route = new GwTenantRoute
        {
            Id = routeId,
            TenantCode = dto.IsGlobal ? "" : dto.TenantCode ?? "",
            ServiceName = dto.ServicePrefix,
            ClusterId = clusterId,
            PathPattern = pathPattern,
            Priority = dto.IsGlobal ? 0 : 10,
            Status = 1,
            IsGlobal = dto.IsGlobal,
            CreatedTime = DateTime.UtcNow
        };
        await _dbContext.TenantRoutes.AddAsync(route);

        await _dbContext.SaveChangesAsync();

        _logger.LogInformation("Registered service {Service} at {Address}", dto.ServicePrefix, dto.ServiceAddress);

        return MapToServiceDto(route, 1);
    }

    public async Task<bool> UnregisterServiceAsync(string serviceName, string? tenantCode = null)
    {
        var route = await _dbContext.TenantRoutes
            .FirstOrDefaultAsync(r =>
                r.ServiceName == serviceName &&
                r.IsDeleted == false &&
                (r.IsGlobal || r.TenantCode == tenantCode));

        if (route == null) return false;

        // Soft delete route
        route.IsDeleted = true;
        route.UpdatedTime = DateTime.UtcNow;

        // Soft delete instances
        var instances = await _dbContext.ServiceInstances
            .Where(i => i.ClusterId == route.ClusterId && !i.IsDeleted)
            .ToListAsync();

        foreach (var instance in instances)
        {
            instance.IsDeleted = true;
            instance.UpdatedTime = DateTime.UtcNow;
        }

        await _dbContext.SaveChangesAsync();

        _logger.LogInformation("Unregistered service {Service}", serviceName);

        return true;
    }

    public async Task<List<GatewayRouteDto>> GetRoutesAsync(bool globalOnly = false)
    {
        var query = _dbContext.TenantRoutes.Where(r => !r.IsDeleted);

        if (globalOnly)
            query = query.Where(r => r.IsGlobal);

        var routes = await query.OrderByDescending(r => r.Priority).ToListAsync();
        var clusters = routes.Select(r => r.ClusterId).Distinct().ToList();

        var instances = await _dbContext.ServiceInstances
            .Where(i => clusters.Contains(i.ClusterId) && !i.IsDeleted)
            .GroupBy(i => i.ClusterId)
            .ToDictionaryAsync(g => g.Key, g => g.Count());

        return routes.Select(r => new GatewayRouteDto
        {
            Id = r.Id,
            ServiceName = r.ServiceName,
            ClusterId = r.ClusterId,
            PathPattern = r.PathPattern,
            Priority = r.Priority,
            IsGlobal = r.IsGlobal,
            TenantCode = r.TenantCode,
            Status = r.Status,
            InstanceCount = instances.GetValueOrDefault(r.ClusterId, 0)
        }).ToList();
    }

    public async Task<GatewayRouteDto> CreateRouteAsync(CreateGatewayRouteDto dto)
    {
        var existing = await _dbContext.TenantRoutes
            .FirstOrDefaultAsync(r =>
                r.ServiceName == dto.ServiceName &&
                r.IsGlobal == dto.IsGlobal &&
                (dto.IsGlobal || r.TenantCode == dto.TenantCode));

        if (existing != null)
        {
            throw new InvalidOperationException($"Route for {dto.ServiceName} already exists");
        }

        var route = new GwTenantRoute
        {
            Id = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
            TenantCode = dto.IsGlobal ? "" : dto.TenantCode ?? "",
            ServiceName = dto.ServiceName,
            ClusterId = dto.ClusterId,
            PathPattern = dto.PathPattern,
            Priority = dto.Priority,
            Status = 1,
            IsGlobal = dto.IsGlobal,
            CreatedTime = DateTime.UtcNow
        };

        await _dbContext.TenantRoutes.AddAsync(route);
        await _dbContext.SaveChangesAsync();

        return new GatewayRouteDto
        {
            Id = route.Id,
            ServiceName = route.ServiceName,
            ClusterId = route.ClusterId,
            PathPattern = route.PathPattern,
            Priority = route.Priority,
            IsGlobal = route.IsGlobal,
            TenantCode = route.TenantCode,
            Status = route.Status,
            InstanceCount = 0
        };
    }

    public async Task<List<GatewayInstanceDto>> GetInstancesAsync(string clusterId)
    {
        var instances = await _dbContext.ServiceInstances
            .Where(i => i.ClusterId == clusterId && !i.IsDeleted)
            .OrderByDescending(i => i.Weight)
            .ToListAsync();

        return instances.Select(i => new GatewayInstanceDto
        {
            Id = i.Id,
            ClusterId = i.ClusterId,
            DestinationId = i.DestinationId,
            Address = i.Address,
            Weight = i.Weight,
            Health = i.Health,
            Status = i.Status,
            CreatedAt = i.CreatedTime
        }).ToList();
    }

    public async Task<GatewayInstanceDto> AddInstanceAsync(CreateGatewayInstanceDto dto)
    {
        var existing = await _dbContext.ServiceInstances
            .FirstOrDefaultAsync(i =>
                i.ClusterId == dto.ClusterId &&
                i.DestinationId == dto.DestinationId &&
                !i.IsDeleted);

        if (existing != null)
        {
            throw new InvalidOperationException($"Instance {dto.DestinationId} already exists in cluster {dto.ClusterId}");
        }

        var instance = new GwServiceInstance
        {
            Id = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
            ClusterId = dto.ClusterId,
            DestinationId = dto.DestinationId,
            Address = dto.Address,
            Weight = dto.Weight,
            Health = 1,
            Status = 1,
            CreatedTime = DateTime.UtcNow
        };

        await _dbContext.ServiceInstances.AddAsync(instance);
        await _dbContext.SaveChangesAsync();

        return new GatewayInstanceDto
        {
            Id = instance.Id,
            ClusterId = instance.ClusterId,
            DestinationId = instance.DestinationId,
            Address = instance.Address,
            Weight = instance.Weight,
            Health = instance.Health,
            Status = instance.Status,
            CreatedAt = instance.CreatedTime
        };
    }

    public async Task<bool> RemoveInstanceAsync(long instanceId)
    {
        var instance = await _dbContext.ServiceInstances.FindAsync(instanceId);
        if (instance == null) return false;

        instance.IsDeleted = true;
        instance.UpdatedTime = DateTime.UtcNow;

        await _dbContext.SaveChangesAsync();
        return true;
    }

    public async Task<bool> UpdateInstanceWeightAsync(long instanceId, int weight)
    {
        var instance = await _dbContext.ServiceInstances.FindAsync(instanceId);
        if (instance == null) return false;

        instance.Weight = weight;
        instance.UpdatedTime = DateTime.UtcNow;

        await _dbContext.SaveChangesAsync();
        return true;
    }

    public async Task ReloadGatewayAsync()
    {
        _logger.LogInformation("Gateway configuration reloaded");
        await Task.CompletedTask;
    }

    private static GatewayServiceDto MapToServiceDto(GwTenantRoute route, int instanceCount = 0)
    {
        return new GatewayServiceDto
        {
            Id = route.Id,
            ServicePrefix = route.ServiceName,
            ServiceName = route.ServiceName,
            ClusterId = route.ClusterId,
            PathPattern = route.PathPattern,
            ServiceAddress = "",
            DestinationId = "",
            Weight = 1,
            InstanceCount = instanceCount,
            IsGlobal = route.IsGlobal,
            TenantCode = route.TenantCode,
            Status = route.Status,
            CreatedAt = route.CreatedTime
        };
    }
}
