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

namespace OTSSignsOrchestrator.Server.Api;

public static class FleetApi
{
    public static void MapFleetEndpoints(this WebApplication app)
    {
        var fleet = app.MapGroup("/api/fleet").RequireAuthorization();
        fleet.MapGet("/", GetFleetSummary);
        fleet.MapGet("/{id:guid}", GetFleetDetail);

        var jobs = app.MapGroup("/api/jobs").RequireAuthorization();
        jobs.MapPost("/", CreateJob);
        jobs.MapGet("/{id:guid}", GetJob);

        app.MapGet("/api/health", () => Results.Ok(new { status = "healthy" }));

        // ── Report endpoints (admin only) ────────────────────────────────────
        var reports = app.MapGroup("/api/reports").RequireAuthorization()
            .RequireAuthorization(policy => policy.RequireRole("admin"));

        reports.MapGet("/billing", GetBillingCsv);
        reports.MapGet("/version-drift", GetVersionDriftCsv);
        reports.MapGet("/fleet-health", GetFleetHealthPdf);
        reports.MapGet("/customer/{id:guid}/usage", GetCustomerUsagePdf);

        fleet.MapPost("/bulk/export-fleet-report", ExportFleetReport)
            .RequireAuthorization(policy => policy.RequireRole("admin"));
    }

    // ── GET /api/fleet ──────────────────────────────────────────────────────
    private static async Task<IResult> GetFleetSummary(OrchestratorDbContext db)
    {
        var customers = await db.Customers
            .AsNoTracking()
            .Include(c => c.Instances)
            .Include(c => c.Jobs)
            .ToListAsync();

        // Get latest health event per instance in one query
        var latestHealth = await db.HealthEvents
            .AsNoTracking()
            .GroupBy(h => h.InstanceId)
            .Select(g => g.OrderByDescending(h => h.OccurredAt).First())
            .ToDictionaryAsync(h => h.InstanceId);

        var result = customers.Select(c =>
        {
            var primaryInstance = c.Instances.FirstOrDefault();
            HealthEvent? health = null;
            if (primaryInstance is not null)
                latestHealth.TryGetValue(primaryInstance.Id, out health);

            return new FleetSummaryDto
            {
                CustomerId = c.Id,
                Abbreviation = c.Abbreviation,
                CompanyName = c.CompanyName,
                Plan = c.Plan.ToString(),
                ScreenCount = c.ScreenCount,
                HealthStatus = health?.Status.ToString() ?? primaryInstance?.HealthStatus.ToString() ?? "Unknown",
                LastHealthCheck = health?.OccurredAt ?? primaryInstance?.LastHealthCheck,
                HasRunningJob = c.Jobs.Any(j => j.Status == JobStatus.Running || j.Status == JobStatus.Queued),
            };
        }).ToArray();

        return Results.Ok(result);
    }

    // ── GET /api/fleet/{id} ─────────────────────────────────────────────────
    private static async Task<IResult> GetFleetDetail(Guid id, OrchestratorDbContext db)
    {
        var customer = await db.Customers
            .AsNoTracking()
            .Include(c => c.Instances)
            .Include(c => c.Jobs.Where(j => j.Status == JobStatus.Running || j.Status == JobStatus.Queued))
            .FirstOrDefaultAsync(c => c.Id == id);

        if (customer is null)
            return Results.NotFound();

        return Results.Ok(new
        {
            customer.Id,
            customer.Abbreviation,
            customer.CompanyName,
            customer.AdminEmail,
            Plan = customer.Plan.ToString(),
            customer.ScreenCount,
            Status = customer.Status.ToString(),
            customer.CreatedAt,
            Instances = customer.Instances.Select(i => new
            {
                i.Id,
                i.XiboUrl,
                i.DockerStackName,
                HealthStatus = i.HealthStatus.ToString(),
                i.LastHealthCheck,
            }),
            ActiveJobs = customer.Jobs.Select(j => new
            {
                j.Id,
                j.JobType,
                Status = j.Status.ToString(),
                j.CreatedAt,
                j.StartedAt,
            }),
        });
    }

    // ── POST /api/jobs ──────────────────────────────────────────────────────
    private static async Task<IResult> CreateJob(
        CreateJobRequest req,
        OrchestratorDbContext db,
        IHubContext<FleetHub, IFleetClient> hub,
        ILogger<CreateJobRequest> logger)
    {
        var customer = await db.Customers.FindAsync(req.CustomerId);
        if (customer is null)
            return Results.NotFound("Customer not found.");

        var job = new Job
        {
            Id = Guid.NewGuid(),
            CustomerId = req.CustomerId,
            JobType = req.JobType,
            Status = JobStatus.Queued,
            TriggeredBy = "operator",
            Parameters = req.Parameters,
            CreatedAt = DateTime.UtcNow,
        };

        db.Jobs.Add(job);
        await db.SaveChangesAsync();

        logger.LogInformation("Job created: {JobId} type={JobType} customer={CustomerId}",
            job.Id, job.JobType, job.CustomerId);

        await hub.Clients.All.SendJobCreated(job.Id.ToString(), customer.Abbreviation, job.JobType);

        return Results.Created($"/api/jobs/{job.Id}", new { job.Id, job.JobType, Status = job.Status.ToString() });
    }

    // ── GET /api/jobs/{id} ──────────────────────────────────────────────────
    private static async Task<IResult> GetJob(Guid id, OrchestratorDbContext db)
    {
        var job = await db.Jobs
            .AsNoTracking()
            .Include(j => j.Steps.OrderBy(s => s.StartedAt))
            .FirstOrDefaultAsync(j => j.Id == id);

        if (job is null)
            return Results.NotFound();

        return Results.Ok(new
        {
            job.Id,
            job.CustomerId,
            job.JobType,
            Status = job.Status.ToString(),
            job.TriggeredBy,
            job.Parameters,
            job.CreatedAt,
            job.StartedAt,
            job.CompletedAt,
            job.ErrorMessage,
            Steps = job.Steps.Select(s => new
            {
                s.Id,
                s.StepName,
                Status = s.Status.ToString(),
                s.LogOutput,
                s.StartedAt,
                s.CompletedAt,
            }),
        });
    }

    // ── GET /api/reports/billing?from=&to= ──────────────────────────────────
    private static async Task<IResult> GetBillingCsv(
        DateOnly from, DateOnly to, BillingReportService billing)
    {
        var csv = await billing.GenerateBillingCsvAsync(from, to);
        return Results.File(csv, "text/csv", $"billing-{from:yyyy-MM-dd}-{to:yyyy-MM-dd}.csv");
    }

    // ── GET /api/reports/version-drift ──────────────────────────────────────
    private static async Task<IResult> GetVersionDriftCsv(BillingReportService billing)
    {
        var csv = await billing.GenerateVersionDriftCsvAsync();
        return Results.File(csv, "text/csv", $"version-drift-{DateTime.UtcNow:yyyy-MM-dd}.csv");
    }

    // ── GET /api/reports/fleet-health?from=&to= ─────────────────────────────
    private static async Task<IResult> GetFleetHealthPdf(
        DateOnly from, DateOnly to, FleetHealthPdfService pdfService)
    {
        var pdf = await pdfService.GenerateFleetHealthPdfAsync(from, to);
        return Results.File(pdf, "application/pdf", $"fleet-health-{from:yyyy-MM-dd}-{to:yyyy-MM-dd}.pdf");
    }

    // ── GET /api/reports/customer/{id}/usage?from=&to= ──────────────────────
    private static async Task<IResult> GetCustomerUsagePdf(
        Guid id, DateOnly from, DateOnly to, FleetHealthPdfService pdfService)
    {
        try
        {
            var pdf = await pdfService.GenerateCustomerUsagePdfAsync(id, from, to);
            return Results.File(pdf, "application/pdf", $"customer-usage-{id}-{from:yyyy-MM-dd}-{to:yyyy-MM-dd}.pdf");
        }
        catch (InvalidOperationException ex)
        {
            return Results.NotFound(ex.Message);
        }
    }

    // ── POST /api/fleet/bulk/export-fleet-report ────────────────────────────
    private static async Task<IResult> ExportFleetReport(FleetHealthPdfService pdfService)
    {
        var to = DateOnly.FromDateTime(DateTime.UtcNow);
        var from = to.AddDays(-7);
        var pdf = await pdfService.GenerateFleetHealthPdfAsync(from, to);
        return Results.File(pdf, "application/pdf", $"fleet-health-{from:yyyy-MM-dd}-{to:yyyy-MM-dd}.pdf");
    }
}

public record FleetSummaryDto
{
    public Guid CustomerId { get; init; }
    public string Abbreviation { get; init; } = string.Empty;
    public string CompanyName { get; init; } = string.Empty;
    public string Plan { get; init; } = string.Empty;
    public int ScreenCount { get; init; }
    public string HealthStatus { get; init; } = "Unknown";
    public DateTime? LastHealthCheck { get; init; }
    public bool HasRunningJob { get; init; }
}

public record CreateJobRequest(Guid CustomerId, string JobType, string? Parameters);
