using System.Text;
using System.Text.Json;

namespace SatelliteProvider.IntegrationTests;

// AZ-808: end-to-end coverage for the region-request endpoint's strict input
// validation. Each test exercises one rule from the validator (FluentValidation
// for business rules, JsonSerializerOptions for wire-format rules) and asserts
// the response body conforms to the RFC 7807 ValidationProblemDetails contract
// in `_docs/02_document/contracts/api/error-shape.md` v1.0.0.
//
// Field names use the post-AZ-812 OSM convention (`lat`/`lon`). The legacy
// `latitude`/`longitude` wire format is verified to be rejected by
// RegionFieldRenameTests.cs (AZ-812 AC-4).
public static class RegionRequestValidationTests
{
    private const string RegionPath = "/api/satellite/request";

    public static async Task RunAll(HttpClient httpClient)
    {
        RouteTestHelpers.PrintTestHeader("Test: Region endpoint strict validation (AZ-808)");

        await HappyPath_Returns200(httpClient);

        // Rule 1: body present
        await EmptyBody_Returns400(httpClient);

        // Rule 2: id required, non-zero Guid
        await MissingId_Returns400(httpClient);
        await ZeroGuidId_Returns400(httpClient);

        // Rule 3: lat required, [-90, 90]
        await MissingLat_Returns400(httpClient);
        await LatOutOfRange_Returns400(httpClient);

        // Rule 4: lon required, [-180, 180]
        await MissingLon_Returns400(httpClient);
        await LonOutOfRange_Returns400(httpClient);

        // Rule 5: sizeMeters required, [100, 10000]
        await MissingSizeMeters_Returns400(httpClient);
        await SizeMetersOutOfRange_Returns400(httpClient);

        // Rule 6: zoomLevel required, [0, 22]
        await MissingZoomLevel_Returns400(httpClient);
        await ZoomLevelOutOfRange_Returns400(httpClient);

        // Rule 7: stitchTiles required (bool, no default)
        await MissingStitchTiles_Returns400(httpClient);

        // Rule 9: type mismatch
        await LatTypeMismatch_Returns400(httpClient);

        // Rule 8 (unknown root fields) is covered by RegionFieldRenameTests (AZ-812 AC-4).

        Console.WriteLine("✓ Region-request validation tests: PASSED");
    }

    private static async Task HappyPath_Returns200(HttpClient httpClient)
    {
        Console.WriteLine();
        Console.WriteLine("AZ-808 AC-2: well-formed request → HTTP 200");

        // Arrange
        var regionId = Guid.NewGuid();
        var body = $"{{\"id\":\"{regionId}\",\"lat\":47.461747,\"lon\":37.647063,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}}";

        // Act
        var response = await PostJsonAsync(httpClient, body);
        var status = (int)response.StatusCode;
        var bodyText = await response.Content.ReadAsStringAsync();

        // Assert
        if (status != 200)
        {
            throw new Exception($"AZ-808 AC-2 happy path: expected HTTP 200, got {status}. Body: {bodyText}");
        }

        Console.WriteLine("  ✓ Well-formed body accepted with HTTP 200");
    }

    private static async Task EmptyBody_Returns400(HttpClient httpClient)
    {
        Console.WriteLine();
        Console.WriteLine("AZ-808 rule 1: empty body → HTTP 400");

        // Arrange
        const string body = "";

        // Act
        var response = await PostJsonAsync(httpClient, body);
        var status = (int)response.StatusCode;

        // Assert
        if (status != 400)
        {
            throw new Exception($"AZ-808 rule 1: expected HTTP 400, got {status}.");
        }

        Console.WriteLine("  ✓ Empty body rejected with HTTP 400");
    }

    private static async Task MissingId_Returns400(HttpClient httpClient)
    {
        Console.WriteLine();
        Console.WriteLine("AZ-808 rule 2 (probe-confirmed gap): missing `id` → HTTP 400 (no silent zero-Guid coercion)");

        // Arrange — the exact 2026-05-22 probe payload that silently coerced to Guid.Empty pre-AZ-808.
        const string body = "{\"lat\":49.94,\"lon\":36.31,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}";

        // Act
        var response = await PostJsonAsync(httpClient, body);
        var problem = await ProblemDetailsAssertions.ReadProblemDetailsAsync(response, "AZ-808 missing id");

        // Assert
        ProblemDetailsAssertions.AssertValidationProblem(
            problem,
            expectedStatus: 400,
            label: "AZ-808 missing id",
            expectedErrorPath: "$",
            expectedErrorContains: "The field value is invalid.");

        Console.WriteLine("  ✓ Missing `id` rejected with HTTP 400 (no silent coercion)");
    }

    private static async Task ZeroGuidId_Returns400(HttpClient httpClient)
    {
        Console.WriteLine();
        Console.WriteLine("AZ-808 rule 2: zero-Guid `id` → HTTP 400");

        // Arrange
        const string body = "{\"id\":\"00000000-0000-0000-0000-000000000000\",\"lat\":47.461747,\"lon\":37.647063,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}";

        // Act
        var response = await PostJsonAsync(httpClient, body);
        var problem = await ProblemDetailsAssertions.ReadProblemDetailsAsync(response, "AZ-808 zero-Guid id");

        // Assert
        ProblemDetailsAssertions.AssertValidationProblem(problem, expectedStatus: 400, label: "AZ-808 zero-Guid id", expectedErrorPath: "id");

        Console.WriteLine("  ✓ Zero-Guid `id` rejected with errors[\"id\"]");
    }

    private static async Task MissingLat_Returns400(HttpClient httpClient)
    {
        Console.WriteLine();
        Console.WriteLine("AZ-808 rule 3: missing `lat` → HTTP 400");

        // Arrange
        var regionId = Guid.NewGuid();
        var body = $"{{\"id\":\"{regionId}\",\"lon\":37.647063,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}}";

        // Act
        var response = await PostJsonAsync(httpClient, body);
        var problem = await ProblemDetailsAssertions.ReadProblemDetailsAsync(response, "AZ-808 missing lat");

        // Assert
        ProblemDetailsAssertions.AssertValidationProblem(
            problem,
            expectedStatus: 400,
            label: "AZ-808 missing lat",
            expectedErrorPath: "$",
            expectedErrorContains: "The field value is invalid.");

        Console.WriteLine("  ✓ Missing `lat` rejected with HTTP 400");
    }

    private static async Task LatOutOfRange_Returns400(HttpClient httpClient)
    {
        Console.WriteLine();
        Console.WriteLine("AZ-808 rule 3: `lat` out of range (-90..90) → HTTP 400");

        // Arrange
        var regionId = Guid.NewGuid();
        var body = $"{{\"id\":\"{regionId}\",\"lat\":91.0,\"lon\":37.647063,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}}";

        // Act
        var response = await PostJsonAsync(httpClient, body);
        var problem = await ProblemDetailsAssertions.ReadProblemDetailsAsync(response, "AZ-808 lat out of range");

        // Assert
        ProblemDetailsAssertions.AssertValidationProblem(problem, expectedStatus: 400, label: "AZ-808 lat out of range", expectedErrorPath: "lat");

        Console.WriteLine("  ✓ `lat=91.0` rejected with errors[\"lat\"]");
    }

    private static async Task MissingLon_Returns400(HttpClient httpClient)
    {
        Console.WriteLine();
        Console.WriteLine("AZ-808 rule 4: missing `lon` → HTTP 400");

        // Arrange
        var regionId = Guid.NewGuid();
        var body = $"{{\"id\":\"{regionId}\",\"lat\":47.461747,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}}";

        // Act
        var response = await PostJsonAsync(httpClient, body);
        var problem = await ProblemDetailsAssertions.ReadProblemDetailsAsync(response, "AZ-808 missing lon");

        // Assert
        ProblemDetailsAssertions.AssertValidationProblem(
            problem,
            expectedStatus: 400,
            label: "AZ-808 missing lon",
            expectedErrorPath: "$",
            expectedErrorContains: "The field value is invalid.");

        Console.WriteLine("  ✓ Missing `lon` rejected with HTTP 400");
    }

    private static async Task LonOutOfRange_Returns400(HttpClient httpClient)
    {
        Console.WriteLine();
        Console.WriteLine("AZ-808 rule 4: `lon` out of range (-180..180) → HTTP 400");

        // Arrange
        var regionId = Guid.NewGuid();
        var body = $"{{\"id\":\"{regionId}\",\"lat\":47.461747,\"lon\":181.0,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}}";

        // Act
        var response = await PostJsonAsync(httpClient, body);
        var problem = await ProblemDetailsAssertions.ReadProblemDetailsAsync(response, "AZ-808 lon out of range");

        // Assert
        ProblemDetailsAssertions.AssertValidationProblem(problem, expectedStatus: 400, label: "AZ-808 lon out of range", expectedErrorPath: "lon");

        Console.WriteLine("  ✓ `lon=181.0` rejected with errors[\"lon\"]");
    }

    private static async Task MissingSizeMeters_Returns400(HttpClient httpClient)
    {
        Console.WriteLine();
        Console.WriteLine("AZ-808 rule 5: missing `sizeMeters` → HTTP 400");

        // Arrange
        var regionId = Guid.NewGuid();
        var body = $"{{\"id\":\"{regionId}\",\"lat\":47.461747,\"lon\":37.647063,\"zoomLevel\":18,\"stitchTiles\":false}}";

        // Act
        var response = await PostJsonAsync(httpClient, body);
        var problem = await ProblemDetailsAssertions.ReadProblemDetailsAsync(response, "AZ-808 missing sizeMeters");

        // Assert
        ProblemDetailsAssertions.AssertValidationProblem(
            problem,
            expectedStatus: 400,
            label: "AZ-808 missing sizeMeters",
            expectedErrorPath: "$",
            expectedErrorContains: "The field value is invalid.");

        Console.WriteLine("  ✓ Missing `sizeMeters` rejected with HTTP 400");
    }

    private static async Task SizeMetersOutOfRange_Returns400(HttpClient httpClient)
    {
        Console.WriteLine();
        Console.WriteLine("AZ-808 rule 5: `sizeMeters` out of range (100..10000) → HTTP 400");

        // Arrange — same 1M cap-exceeder used by SEC-03; this validator replaces the old inline check.
        var regionId = Guid.NewGuid();
        var body = $"{{\"id\":\"{regionId}\",\"lat\":47.461747,\"lon\":37.647063,\"sizeMeters\":1000000,\"zoomLevel\":18,\"stitchTiles\":false}}";

        // Act
        var response = await PostJsonAsync(httpClient, body);
        var problem = await ProblemDetailsAssertions.ReadProblemDetailsAsync(response, "AZ-808 sizeMeters out of range");

        // Assert
        ProblemDetailsAssertions.AssertValidationProblem(problem, expectedStatus: 400, label: "AZ-808 sizeMeters out of range", expectedErrorPath: "sizeMeters");

        Console.WriteLine("  ✓ `sizeMeters=1000000` rejected with errors[\"sizeMeters\"]");
    }

    private static async Task MissingZoomLevel_Returns400(HttpClient httpClient)
    {
        Console.WriteLine();
        Console.WriteLine("AZ-808 rule 6: missing `zoomLevel` → HTTP 400");

        // Arrange
        var regionId = Guid.NewGuid();
        var body = $"{{\"id\":\"{regionId}\",\"lat\":47.461747,\"lon\":37.647063,\"sizeMeters\":200,\"stitchTiles\":false}}";

        // Act
        var response = await PostJsonAsync(httpClient, body);
        var problem = await ProblemDetailsAssertions.ReadProblemDetailsAsync(response, "AZ-808 missing zoomLevel");

        // Assert
        ProblemDetailsAssertions.AssertValidationProblem(
            problem,
            expectedStatus: 400,
            label: "AZ-808 missing zoomLevel",
            expectedErrorPath: "$",
            expectedErrorContains: "The field value is invalid.");

        Console.WriteLine("  ✓ Missing `zoomLevel` rejected with HTTP 400");
    }

    private static async Task ZoomLevelOutOfRange_Returns400(HttpClient httpClient)
    {
        Console.WriteLine();
        Console.WriteLine("AZ-808 rule 6: `zoomLevel` out of range (0..22) → HTTP 400");

        // Arrange
        var regionId = Guid.NewGuid();
        var body = $"{{\"id\":\"{regionId}\",\"lat\":47.461747,\"lon\":37.647063,\"sizeMeters\":200,\"zoomLevel\":30,\"stitchTiles\":false}}";

        // Act
        var response = await PostJsonAsync(httpClient, body);
        var problem = await ProblemDetailsAssertions.ReadProblemDetailsAsync(response, "AZ-808 zoomLevel out of range");

        // Assert
        ProblemDetailsAssertions.AssertValidationProblem(problem, expectedStatus: 400, label: "AZ-808 zoomLevel out of range", expectedErrorPath: "zoomLevel");

        Console.WriteLine("  ✓ `zoomLevel=30` rejected with errors[\"zoomLevel\"]");
    }

    private static async Task MissingStitchTiles_Returns400(HttpClient httpClient)
    {
        Console.WriteLine();
        Console.WriteLine("AZ-808 rule 7: missing `stitchTiles` → HTTP 400 (no defaulting to false)");

        // Arrange
        var regionId = Guid.NewGuid();
        var body = $"{{\"id\":\"{regionId}\",\"lat\":47.461747,\"lon\":37.647063,\"sizeMeters\":200,\"zoomLevel\":18}}";

        // Act
        var response = await PostJsonAsync(httpClient, body);
        var problem = await ProblemDetailsAssertions.ReadProblemDetailsAsync(response, "AZ-808 missing stitchTiles");

        // Assert
        ProblemDetailsAssertions.AssertValidationProblem(
            problem,
            expectedStatus: 400,
            label: "AZ-808 missing stitchTiles",
            expectedErrorPath: "$",
            expectedErrorContains: "The field value is invalid.");

        Console.WriteLine("  ✓ Missing `stitchTiles` rejected with HTTP 400");
    }

    private static async Task LatTypeMismatch_Returns400(HttpClient httpClient)
    {
        Console.WriteLine();
        Console.WriteLine("AZ-808 rule 9: type mismatch (`lat` as string) → HTTP 400");

        // Arrange
        var regionId = Guid.NewGuid();
        var body = $"{{\"id\":\"{regionId}\",\"lat\":\"fifty\",\"lon\":37.647063,\"sizeMeters\":200,\"zoomLevel\":18,\"stitchTiles\":false}}";

        // Act
        var response = await PostJsonAsync(httpClient, body);
        var problem = await ProblemDetailsAssertions.ReadProblemDetailsAsync(response, "AZ-808 lat type mismatch");

        // Assert
        ProblemDetailsAssertions.AssertValidationProblem(problem, expectedStatus: 400, label: "AZ-808 lat type mismatch");

        Console.WriteLine("  ✓ `lat:\"fifty\"` rejected with HTTP 400");
    }

    private static Task<HttpResponseMessage> PostJsonAsync(HttpClient httpClient, string body)
    {
        var content = new StringContent(body, Encoding.UTF8, "application/json");
        return httpClient.PostAsync(RegionPath, content);
    }
}
