using Microsoft.AspNetCore.SignalR;
using OTSSignsOrchestrator.Server.Data;
using OTSSignsOrchestrator.Server.Data.Entities;
using OTSSignsOrchestrator.Server.Hubs;

namespace OTSSignsOrchestrator.Server.Workers;

/// <summary>
/// Helper that wraps pipeline step execution with <see cref="JobStep"/> lifecycle management:
/// creates the row, sets Running, captures output, marks Completed/Failed, and broadcasts
/// progress via SignalR.
/// </summary>
public sealed class StepRunner
{
    private readonly OrchestratorDbContext _db;
    private readonly IHubContext<FleetHub, IFleetClient> _hub;
    private readonly ILogger _logger;
    private readonly Guid _jobId;
    private readonly int _totalSteps;
    private int _currentStep;

    public StepRunner(
        OrchestratorDbContext db,
        IHubContext<FleetHub, IFleetClient> hub,
        ILogger logger,
        Guid jobId,
        int totalSteps)
    {
        _db = db;
        _hub = hub;
        _logger = logger;
        _jobId = jobId;
        _totalSteps = totalSteps;
    }

    /// <summary>
    /// Execute a named step, persisting a <see cref="JobStep"/> record and broadcasting progress.
    /// </summary>
    /// <param name="stepName">Short identifier for the step (e.g. "mysql-setup").</param>
    /// <param name="action">
    /// Async delegate that performs the work. Return a log string summarising what happened.
    /// </param>
    /// <param name="ct">Cancellation token.</param>
    public async Task RunAsync(string stepName, Func<Task<string>> action, CancellationToken ct)
    {
        _currentStep++;
        var pct = (int)((double)_currentStep / _totalSteps * 100);

        var step = new JobStep
        {
            Id = Guid.NewGuid(),
            JobId = _jobId,
            StepName = stepName,
            Status = JobStepStatus.Running,
            StartedAt = DateTime.UtcNow,
        };

        _db.JobSteps.Add(step);
        await _db.SaveChangesAsync(ct);

        _logger.LogInformation("Job {JobId}: step [{Step}/{Total}] {StepName} started",
            _jobId, _currentStep, _totalSteps, stepName);

        await _hub.Clients.All.SendJobProgressUpdate(
            _jobId.ToString(), stepName, pct, $"Starting {stepName}…");

        try
        {
            var logOutput = await action();

            step.Status = JobStepStatus.Completed;
            step.LogOutput = logOutput;
            step.CompletedAt = DateTime.UtcNow;
            await _db.SaveChangesAsync(ct);

            _logger.LogInformation("Job {JobId}: step {StepName} completed", _jobId, stepName);

            await _hub.Clients.All.SendJobProgressUpdate(
                _jobId.ToString(), stepName, pct, logOutput);
        }
        catch (Exception ex)
        {
            step.Status = JobStepStatus.Failed;
            step.LogOutput = ex.Message;
            step.CompletedAt = DateTime.UtcNow;
            await _db.SaveChangesAsync(CancellationToken.None);

            _logger.LogError(ex, "Job {JobId}: step {StepName} failed", _jobId, stepName);

            await _hub.Clients.All.SendJobProgressUpdate(
                _jobId.ToString(), stepName, pct, $"FAILED: {ex.Message}");

            throw; // re-throw to fail the job
        }
    }
}
