using System.Text.Json;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using OTSSignsOrchestrator.Server.Clients;
using OTSSignsOrchestrator.Server.Data;
using OTSSignsOrchestrator.Server.Data.Entities;
using OTSSignsOrchestrator.Server.Hubs;
using OTSSignsOrchestrator.Core.Services;

namespace OTSSignsOrchestrator.Server.Workers;

/// <summary>
/// Updates the Xibo CMS screen limit and records a snapshot.
/// Handles <c>JobType = "update-screen-limit"</c>.
///
/// Steps:
///   1. update-settings — PUT /api/settings with new MAX_LICENSED_DISPLAYS
///   2. update-snapshot — Record new screen count in ScreenSnapshots for today
///   3. audit-log       — Append-only AuditLog entry
/// </summary>
public sealed class UpdateScreenLimitPipeline : IProvisioningPipeline
{
    public string HandlesJobType => "update-screen-limit";

    private const int TotalSteps = 3;

    private readonly IServiceProvider _services;
    private readonly ILogger<UpdateScreenLimitPipeline> _logger;

    public UpdateScreenLimitPipeline(
        IServiceProvider services,
        ILogger<UpdateScreenLimitPipeline> logger)
    {
        _services = services;
        _logger = logger;
    }

    public async Task ExecuteAsync(Job job, CancellationToken ct)
    {
        await using var scope = _services.CreateAsyncScope();
        var db = scope.ServiceProvider.GetRequiredService<OrchestratorDbContext>();
        var hub = scope.ServiceProvider.GetRequiredService<IHubContext<FleetHub, IFleetClient>>();
        var xiboFactory = scope.ServiceProvider.GetRequiredService<XiboClientFactory>();
        var settings = scope.ServiceProvider.GetRequiredService<SettingsService>();

        var ctx = await BuildContextAsync(job, db, ct);
        var runner = new StepRunner(db, hub, _logger, job.Id, TotalSteps);

        var abbrev = ctx.Abbreviation;

        // Parse newScreenCount from Job.Parameters JSON
        var newScreenCount = ParseScreenCount(ctx.ParametersJson)
            ?? throw new InvalidOperationException(
                "Job.Parameters must contain 'newScreenCount' (integer) for update-screen-limit.");

        // Get Xibo API client via XiboClientFactory
        var oauthReg = await db.OauthAppRegistries
            .Where(r => r.InstanceId == ctx.InstanceId)
            .OrderByDescending(r => r.CreatedAt)
            .FirstOrDefaultAsync(ct)
            ?? throw new InvalidOperationException(
                $"No OauthAppRegistry found for instance {ctx.InstanceId}.");

        var oauthSecret = await settings.GetAsync(SettingsService.InstanceOAuthSecretId(abbrev))
            ?? throw new InvalidOperationException(
                $"OAuth secret not found in settings for instance '{abbrev}'.");

        var xiboClient = await xiboFactory.CreateAsync(ctx.InstanceUrl, oauthReg.ClientId, oauthSecret);

        // ── Step 1: update-settings ─────────────────────────────────────────
        await runner.RunAsync("update-settings", async () =>
        {
            var response = await xiboClient.UpdateSettingsAsync(new UpdateSettingsRequest(
                new Dictionary<string, string>
                {
                    ["MAX_LICENSED_DISPLAYS"] = newScreenCount.ToString(),
                }));

            EnsureSuccess(response);
            return $"Xibo MAX_LICENSED_DISPLAYS updated to {newScreenCount} for {ctx.InstanceUrl}.";
        }, ct);

        // ── Step 2: update-snapshot ─────────────────────────────────────────
        await runner.RunAsync("update-snapshot", async () =>
        {
            var today = DateOnly.FromDateTime(DateTime.UtcNow);

            // Upsert: if a snapshot already exists for today, update it; otherwise insert
            var existing = await db.ScreenSnapshots
                .FirstOrDefaultAsync(s => s.InstanceId == ctx.InstanceId && s.SnapshotDate == today, ct);

            if (existing is not null)
            {
                existing.ScreenCount = newScreenCount;
            }
            else
            {
                db.ScreenSnapshots.Add(new ScreenSnapshot
                {
                    Id = Guid.NewGuid(),
                    InstanceId = ctx.InstanceId,
                    SnapshotDate = today,
                    ScreenCount = newScreenCount,
                    CreatedAt = DateTime.UtcNow,
                });
            }

            // Also update Customer.ScreenCount to reflect the new limit
            var customer = await db.Customers.FirstOrDefaultAsync(c => c.Id == ctx.CustomerId, ct);
            if (customer is not null)
                customer.ScreenCount = newScreenCount;

            await db.SaveChangesAsync(ct);
            return $"ScreenSnapshot recorded for {today}: {newScreenCount} screens.";
        }, ct);

        // ── Step 3: audit-log ───────────────────────────────────────────────
        await runner.RunAsync("audit-log", async () =>
        {
            db.AuditLogs.Add(new AuditLog
            {
                Id = Guid.NewGuid(),
                InstanceId = ctx.InstanceId,
                Actor = "system/stripe-webhook",
                Action = "update-screen-limit",
                Target = $"xibo-{abbrev}",
                Outcome = "success",
                Detail = $"Screen limit updated to {newScreenCount}. Job {job.Id}.",
                OccurredAt = DateTime.UtcNow,
            });
            await db.SaveChangesAsync(ct);

            return "AuditLog entry written for screen limit update.";
        }, ct);

        _logger.LogInformation(
            "UpdateScreenLimitPipeline completed for job {JobId} (abbrev={Abbrev}, screens={Count})",
            job.Id, abbrev, newScreenCount);
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Helpers
    // ─────────────────────────────────────────────────────────────────────────

    private static int? ParseScreenCount(string? parametersJson)
    {
        if (string.IsNullOrEmpty(parametersJson)) return null;

        using var doc = JsonDocument.Parse(parametersJson);
        if (doc.RootElement.TryGetProperty("newScreenCount", out var prop) && prop.TryGetInt32(out var count))
            return count;

        return null;
    }

    private static async Task<PipelineContext> BuildContextAsync(Job job, OrchestratorDbContext db, CancellationToken ct)
    {
        var customer = await db.Customers
            .Include(c => c.Instances)
            .FirstOrDefaultAsync(c => c.Id == job.CustomerId, ct)
            ?? throw new InvalidOperationException($"Customer {job.CustomerId} not found for job {job.Id}.");

        var instance = customer.Instances.FirstOrDefault()
            ?? throw new InvalidOperationException($"No instance found for customer {job.CustomerId}.");

        var abbrev = customer.Abbreviation.ToLowerInvariant();

        return new PipelineContext
        {
            JobId = job.Id,
            CustomerId = customer.Id,
            InstanceId = instance.Id,
            Abbreviation = abbrev,
            CompanyName = customer.CompanyName,
            AdminEmail = customer.AdminEmail,
            AdminFirstName = customer.AdminFirstName,
            InstanceUrl = instance.XiboUrl,
            DockerStackName = instance.DockerStackName,
            ParametersJson = job.Parameters,
        };
    }

    private static void EnsureSuccess<T>(Refit.IApiResponse<T> response)
    {
        if (!response.IsSuccessStatusCode)
            throw new InvalidOperationException(
                $"API call failed with status {response.StatusCode}: {response.Error?.Content}");
    }
}
