using Cronos;
using IWS5.Core.DTO;
using IWS5.Core.DTO.IAdmin.Schedules;
using IWS5.Core.DTO.IAdmin.Servers;
using IWS5.Core.Enums;
using IWS5.Core.Enums.IAdmin;
using IWS5.Core.Models;
using IWS5.Core.Services.IAdmin;
using IWS5.DatabaseAccess.Interfaces.IAdmin;
using IWS5.DatabaseAccess.IwsDataModels;
using Serilog;

namespace IWS5.Services.IAdminServices;

public class ServersService(IServerRepository serverRepository, ILogger logger) : IServersService
{
    public async Task<CodeResponse<List<ServerDetailsDTO>>> GetServersAsync()
    {
        logger.Information("Fetching all servers");

        var servers = await serverRepository.ListAsync();

        var dtos = servers.Select(s => MapServerDTO(s));

        return new CodeResponse<List<ServerDetailsDTO>>(ResponseCode.OK, null, dtos.ToList());
    }

    public async Task<CodeResponse<ServerDetailsDTO>> GetServerDetailsAsync(string hostName)
    {
        logger.Information($"Fetching details about server with host name: {hostName}");

        var server = await serverRepository.GetByNameAsync(hostName);
        if (server == null)
        {
            logger.Warning($"Server with host name {hostName} not found.");
            return new CodeResponse<ServerDetailsDTO>(ResponseCode.NotFound, "Server not found", null);
        }

        var dto = MapServerDTO(server, true, true);
        return new CodeResponse<ServerDetailsDTO>(ResponseCode.OK, null, dto);
    }

    public async Task<CodeResponse<ServerDetailsDTO>> CreateServerAsync(CreateServerRequestDTO createServerRequest)
    {
        logger.Information($"Creating new server");

        var server = new Atserver
        {
            HostName = createServerRequest.HostName,
            LogLevel = createServerRequest.LogLevel,
            LogFile = createServerRequest.LogFile,
            Limit = createServerRequest.Limit,
            Interval = createServerRequest.Interval,
            Status = "Stopped",
            RunExpired = false
        };
        await serverRepository.AddAsync(server);

        var dto = MapServerDTO(server);
        return new CodeResponse<ServerDetailsDTO>(ResponseCode.OK, null, dto);
    }

    public async Task<CodeResponse<ServerDetailsDTO>> UpdateServerAsync(string hostName, UpdateServerRequestDTO updateServerRequest)
    {
        logger.Information($"Updating existing server with host name: {hostName}");

        var server = await serverRepository.GetByNameAsync(hostName);
        if (server == null)
        {
            logger.Warning($"Server with host name {hostName} not found.");
            return new CodeResponse<ServerDetailsDTO>(ResponseCode.NotFound, "Server not found", null);
        }

        server.LogLevel = updateServerRequest.LogLevel;
        server.LogFile = updateServerRequest.LogFile;
        server.Limit = updateServerRequest.Limit;
        server.Interval = updateServerRequest.Interval;

        await serverRepository.UpdateAsync(server);

        var dto = MapServerDTO(server, true, true);
        return new CodeResponse<ServerDetailsDTO>(ResponseCode.OK, null, dto);
    }

    public async Task<bool> DeleteServerAsync(string hostName)
    {
        logger.Information($"Deleting existing server with host name: {hostName}");

        var server = await serverRepository.GetByNameAsync(hostName);
        await serverRepository.DeleteAsync(server);

        return true;
    }

    private static ServerDetailsDTO MapServerDTO(Atserver server, bool includeSchedules = false, bool includeEvents = false)
    {
        var schedules = new List<ScheduleDetailsDTO>();
        if (includeSchedules)
        {
            schedules = schedules.Concat(
                server.AtscheduledItems.Select(si =>
                    new ScheduleDetailsDTO(
                        si.ScheduleId,
                        si.Name,
                        si.ServerHost,
                        si.Description,
                        si.Enabled,
                        si.Priority.GetValueOrDefault(),
                        si.WaitForCompletion,
                        Enum.GetName(typeof(Schedule), si.ScheduleTypeId),
                        si.ScheduleTypeId,
                        si.Status.StatusName,
                        si.LastRun,
                        !string.IsNullOrEmpty(si.RecurrenceExpression) ? CronExpression.Parse(si.RecurrenceExpression).GetNextOccurrence(DateTime.UtcNow) : null,
                        si.LastRunStatus?.StatusName,
                        new List<ScheduleTaskDTO>(),
                        si.RecurrenceExpression,
                        si.StartTime,
                        si.ExpireTime,
                        si.CreatedAt,
                        si.UpdatedAt
                    ))
                .ToList()
            ).ToList();
        }

        var events = new List<EventDefinitionDTO>();
        if (includeEvents)
        {
            events = events.Concat(
                server.AteventDefinitions.Select(ed =>
                    new EventDefinitionDTO(
                        ed.EventId,
                        ed.ProcessId,
                        ed.EventName,
                        ed.PositionX,
                        ed.PositionY,
                        ed.EventTypeId,
                        ed.Server,
                        ed.ExpirationInterval,
                        ed.JoinEventId,
                        ed.ProcessAtServer,
                        ed.Script,
                        ed.ScriptLanguage,
                        ed.ExScript,
                        ed.ExpirationInstance,
                        ed.NextEventId,
                        ed.Action
                    ))
                .ToList()
            ).ToList();
        }

        return new ServerDetailsDTO(server.HostName,
            server.LogLevel.GetValueOrDefault(),
            server.LogFile,
            server.Limit.GetValueOrDefault(),
            server.Interval.GetValueOrDefault(),
            server.Status,
            server.RunExpired.GetValueOrDefault(),
            schedules,
            events);
    }
}

