using System.Security.Cryptography.X509Certificates;
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.Server.Workers;

namespace OTSSignsOrchestrator.Server.Api;

public static class CustomerPortalApi
{
    private const int MinCertDaysRemaining = 30;

    public static void MapCustomerPortalEndpoints(this WebApplication app)
    {
        var portal = app.MapGroup("/api/portal/byoi")
            .RequireAuthorization("CustomerPortal");

        portal.MapPost("/configure", HandleConfigureByoi);
        portal.MapGet("/sp-metadata", HandleGetSpMetadata);
        portal.MapPost("/rotate-cert", HandleRotateCert);
    }

    // ── POST /api/portal/byoi/configure ─────────────────────────────────────
    private static async Task<IResult> HandleConfigureByoi(
        ConfigureByoiRequest req,
        OrchestratorDbContext db,
        IHubContext<FleetHub, IFleetClient> hub,
        HttpContext httpContext,
        ILogger<ConfigureByoiRequest> logger)
    {
        // Resolve customer from the authenticated JWT
        var customer = await ResolveCustomerAsync(httpContext, db);
        if (customer is null)
            return Results.Forbid();

        if (customer.Plan != CustomerPlan.Pro)
            return Results.Problem("BYOI SAML is only available for Pro tier customers.", statusCode: 403);

        // Validate cert PEM
        var certValidation = ValidateCertPem(req.CertPem);
        if (certValidation is not null)
            return Results.ValidationProblem(
                new Dictionary<string, string[]> { ["certPem"] = [certValidation] });

        // Validate required fields
        var errors = new Dictionary<string, string[]>();
        if (string.IsNullOrWhiteSpace(req.SsoUrl))
            errors["ssoUrl"] = ["ssoUrl is required."];
        if (string.IsNullOrWhiteSpace(req.IdpEntityId))
            errors["idpEntityId"] = ["idpEntityId is required."];
        if (errors.Count > 0)
            return Results.ValidationProblem(errors);

        // Create a provision-byoi Job
        var parametersJson = JsonSerializer.Serialize(new ByoiParameters
        {
            CustomerCertPem = req.CertPem!,
            CustomerSsoUrl = req.SsoUrl!,
            CustomerIdpEntityId = req.IdpEntityId!,
            CustomerSloUrl = req.SloUrl,
        });

        var job = new Job
        {
            Id = Guid.NewGuid(),
            CustomerId = customer.Id,
            JobType = "provision-byoi",
            Status = JobStatus.Queued,
            TriggeredBy = $"customer-portal:{customer.AdminEmail}",
            Parameters = parametersJson,
            CreatedAt = DateTime.UtcNow,
        };

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

        logger.LogInformation("BYOI configure job {JobId} created for customer {CustomerId}",
            job.Id, customer.Id);

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

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

    // ── GET /api/portal/byoi/sp-metadata ────────────────────────────────────
    private static async Task<IResult> HandleGetSpMetadata(
        OrchestratorDbContext db,
        IAuthentikClient authentikClient,
        HttpContext httpContext,
        ILogger<ConfigureByoiRequest> logger)
    {
        var customer = await ResolveCustomerAsync(httpContext, db);
        if (customer is null)
            return Results.Forbid();

        var instance = customer.Instances.FirstOrDefault();
        if (instance is null)
            return Results.NotFound("No instance found for this customer.");

        var byoiConfig = await db.ByoiConfigs
            .AsNoTracking()
            .FirstOrDefaultAsync(b => b.InstanceId == instance.Id && b.Enabled);

        if (byoiConfig is null)
            return Results.NotFound("No BYOI configuration found for this instance.");

        var metadataResponse = await authentikClient.GetSamlSourceMetadataAsync(byoiConfig.Slug);
        if (!metadataResponse.IsSuccessStatusCode || metadataResponse.Content is null)
        {
            logger.LogError("Failed to fetch SP metadata for slug {Slug}: {Error}",
                byoiConfig.Slug, metadataResponse.Error?.Content ?? metadataResponse.ReasonPhrase);
            return Results.Problem("Failed to retrieve SP metadata from Authentik.", statusCode: 502);
        }

        return Results.Content(metadataResponse.Content, "application/xml");
    }

    // ── POST /api/portal/byoi/rotate-cert ───────────────────────────────────
    private static async Task<IResult> HandleRotateCert(
        RotateCertRequest req,
        OrchestratorDbContext db,
        IHubContext<FleetHub, IFleetClient> hub,
        HttpContext httpContext,
        ILogger<RotateCertRequest> logger)
    {
        var customer = await ResolveCustomerAsync(httpContext, db);
        if (customer is null)
            return Results.Forbid();

        if (customer.Plan != CustomerPlan.Pro)
            return Results.Problem("BYOI SAML is only available for Pro tier customers.", statusCode: 403);

        // Validate cert PEM
        var certValidation = ValidateCertPem(req.CertPem);
        if (certValidation is not null)
            return Results.ValidationProblem(
                new Dictionary<string, string[]> { ["certPem"] = [certValidation] });

        var instance = customer.Instances.FirstOrDefault();
        if (instance is null)
            return Results.NotFound("No instance found for this customer.");

        var existingConfig = await db.ByoiConfigs
            .FirstOrDefaultAsync(b => b.InstanceId == instance.Id && b.Enabled);

        if (existingConfig is null)
            return Results.NotFound("No active BYOI configuration found to rotate.");

        // Create a re-provisioning job with the new cert
        var parametersJson = JsonSerializer.Serialize(new ByoiParameters
        {
            CustomerCertPem = req.CertPem!,
            CustomerSsoUrl = existingConfig.SsoUrl,
            CustomerIdpEntityId = existingConfig.EntityId,
            CustomerSloUrl = null,
        });

        var job = new Job
        {
            Id = Guid.NewGuid(),
            CustomerId = customer.Id,
            JobType = "provision-byoi",
            Status = JobStatus.Queued,
            TriggeredBy = $"customer-portal:cert-rotate:{customer.AdminEmail}",
            Parameters = parametersJson,
            CreatedAt = DateTime.UtcNow,
        };

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

        logger.LogInformation("BYOI cert rotate job {JobId} created for customer {CustomerId}",
            job.Id, customer.Id);

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

        return Results.Ok(new { jobId = job.Id });
    }

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

    /// <summary>
    /// Validates a PEM certificate string. Returns an error message on failure, or null if valid.
    /// Rejects self-signed, expired, and certs expiring in &lt; 30 days.
    /// </summary>
    private static string? ValidateCertPem(string? certPem)
    {
        if (string.IsNullOrWhiteSpace(certPem))
            return "certPem is required.";

        X509Certificate2 cert;
        try
        {
            var base64 = certPem
                .Replace("-----BEGIN CERTIFICATE-----", "")
                .Replace("-----END CERTIFICATE-----", "")
                .Replace("\r", "")
                .Replace("\n", "")
                .Trim();

            cert = X509CertificateLoader.LoadCertificate(Convert.FromBase64String(base64));
        }
        catch (Exception)
        {
            return "Invalid certificate PEM format.";
        }

        using (cert)
        {
            if (cert.NotAfter.ToUniversalTime() < DateTime.UtcNow)
                return "Certificate has already expired.";

            if ((cert.NotAfter.ToUniversalTime() - DateTime.UtcNow).TotalDays < MinCertDaysRemaining)
                return $"Certificate expires in less than {MinCertDaysRemaining} days. Provide a certificate with a longer validity period.";

            // Reject self-signed: issuer == subject
            if (string.Equals(cert.Issuer, cert.Subject, StringComparison.OrdinalIgnoreCase))
                return "Self-signed certificates are not accepted. Provide a CA-signed certificate.";
        }

        return null;
    }

    /// <summary>
    /// Resolves the current customer from the authenticated JWT claims.
    /// Expects a "customer_id" claim in the token.
    /// </summary>
    private static async Task<Customer?> ResolveCustomerAsync(HttpContext httpContext, OrchestratorDbContext db)
    {
        var customerIdClaim = httpContext.User.FindFirst("customer_id")?.Value;
        if (customerIdClaim is null || !Guid.TryParse(customerIdClaim, out var customerId))
            return null;

        return await db.Customers
            .Include(c => c.Instances)
            .FirstOrDefaultAsync(c => c.Id == customerId);
    }
}

// ── Request DTOs ────────────────────────────────────────────────────────────
public record ConfigureByoiRequest(string? CertPem, string? SsoUrl, string? IdpEntityId, string? SloUrl);
public record RotateCertRequest(string? CertPem);
