using Hcs.WebApp.BackgroundServices.CampaignManagers;
using Hcs.WebApp.Data.Hcs;
using Hcs.WebApp.Services;

namespace Hcs.WebApp.BackgroundServices
{
    public class CampaignManagementService(
        CampaignManagementState campaignManagementState,
        OperationExecutionState operationExecutionState,
        ResultGetState resultGetState,
        DataParsingState dataParsingState,
        ManagerFactory managerFactory,
        IServiceScopeFactory scopeFactory) : BackgroundService
    {
        private const int STATES_WAIT_TIME = 1000;
        private const int ITERATION_TIME = 1000;
        private const int SLEEP_TIME = 30000;

        private readonly CampaignManagementState campaignManagementState = campaignManagementState;
        private readonly OperationExecutionState operationExecutionState = operationExecutionState;
        private readonly ResultGetState resultGetState = resultGetState;
        private readonly DataParsingState dataParsingState = dataParsingState;
        private readonly ManagerFactory managerFactory = managerFactory;
        private readonly IServiceScopeFactory scopeFactory = scopeFactory;
        private readonly List<IManager> managers = [];

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            await WaitForOtherStates();
            await InitializeStateAsync();

            while (!stoppingToken.IsCancellationRequested)
            {
                while (campaignManagementState.TryDequeueCampaign(out var campaign))
                {
                    if (stoppingToken.IsCancellationRequested) return;

                    var manager = managerFactory.CreateManager(campaign);
                    manager.OnCampaignStarted += OnCampaignStarted;
                    manager.OnCampaignProgressStep += OnCampaignProgressStep;
                    manager.OnCampaignEnded += OnCampaignEnded;

                    managers.Add(manager);
                }

                foreach (var manager in managers)
                {
                    try
                    {
                        await manager.ProcessAsync();
                    }
                    catch (Exception e)
                    {
                        await manager.EndWithFailAsync(e);
                    }

                    if (manager.State == IManager.ManagerState.Ended)
                    {
                        manager.OnCampaignStarted -= OnCampaignStarted;
                        manager.OnCampaignProgressStep -= OnCampaignProgressStep;
                        manager.OnCampaignEnded -= OnCampaignEnded;
                    }
                }

                managers.RemoveAll(x => x.State == IManager.ManagerState.Ended);

                if (managers.Count > 0)
                {
                    await Task.Delay(ITERATION_TIME, stoppingToken);
                }
                else
                {
                    await Task.Delay(SLEEP_TIME, stoppingToken);
                }
            }
        }

        private async Task WaitForOtherStates()
        {
            while (!operationExecutionState.Ready || !resultGetState.Ready || !dataParsingState.Ready)
            {
                await Task.Delay(STATES_WAIT_TIME);
            }
        }

        private async Task InitializeStateAsync()
        {
            using var scope = scopeFactory.CreateScope();
            var headquartersService = scope.ServiceProvider.GetRequiredService<HeadquartersService>();

            var campaigns = await headquartersService.GetNotEndedCampaignsAsync();
            foreach (var campaign in campaigns)
            {
                campaignManagementState.EnqueueCampaign(campaign);
            }
        }

        private void OnCampaignStarted(int campaignId, DateTime startedAt)
        {
            campaignManagementState.InvokeOnCampaignStarted(campaignId, startedAt);
        }

        private void OnCampaignProgressStep(int campaignId, int step, int progress)
        {
            campaignManagementState.InvokeOnCampaignProgressStep(campaignId, step, progress);
        }

        private void OnCampaignEnded(int campaignId, Campaign.CampaignType type, DateTime endedAt, string failureReason)
        {
            campaignManagementState.InvokeOnCampaignEnded(campaignId, type, endedAt, failureReason);
        }
    }
}
