package de.effigenix.loadtest.scenario;

import de.effigenix.loadtest.infrastructure.LoadTestDataSeeder;
import de.effigenix.loadtest.util.JsonBodyBuilder;
import io.gatling.javaapi.core.ChainBuilder;
import io.gatling.javaapi.core.ScenarioBuilder;

import java.util.concurrent.ThreadLocalRandom;

import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;

/**
 * Produktions-Szenario: Rezepte, Chargen, Produktionsaufträge.
 * Bildet den End-to-End-Workflow der Produktion ab.
 */
public final class ProductionScenario {

    private ProductionScenario() {}

    // ---- Rezepte ----

    public static ChainBuilder listRecipes() {
        return exec(
                http("Rezepte auflisten")
                        .get("/api/recipes")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().is(200))
        );
    }

    public static ChainBuilder getRandomRecipe() {
        return exec(session -> {
            var ids = LoadTestDataSeeder.recipeIds();
            String id = ids.get(ThreadLocalRandom.current().nextInt(ids.size()));
            return session.set("recipeId", id);
        }).exec(
                http("Rezept laden")
                        .get("/api/recipes/#{recipeId}")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().is(200))
        );
    }

    // ---- Chargen (Batches) ----

    public static ChainBuilder listBatches() {
        return exec(
                http("Chargen auflisten")
                        .get("/api/production/batches")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().is(200))
        );
    }

    public static ChainBuilder planBatch() {
        return exec(session -> {
            var ids = LoadTestDataSeeder.recipeIds();
            String recipeId = ids.get(ThreadLocalRandom.current().nextInt(ids.size()));
            return session.set("planBatchBody", JsonBodyBuilder.planBatchBody(recipeId));
        }).exec(
                http("Charge planen")
                        .post("/api/production/batches")
                        .header("Authorization", "Bearer #{accessToken}")
                        .header("Content-Type", "application/json")
                        .body(StringBody("#{planBatchBody}"))
                        .check(status().in(200, 201, 400, 409, 500))
                        .check(jsonPath("$.id").optional().saveAs("batchId"))
        );
    }

    public static ChainBuilder startBatch() {
        return exec(
                http("Charge starten")
                        .post("/api/production/batches/#{batchId}/start")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().in(200, 400, 409, 500))
        );
    }

    public static ChainBuilder completeBatch() {
        return exec(
                http("Charge abschließen")
                        .post("/api/production/batches/#{batchId}/complete")
                        .header("Authorization", "Bearer #{accessToken}")
                        .header("Content-Type", "application/json")
                        .body(StringBody(JsonBodyBuilder.completeBatchBody()))
                        .check(status().in(200, 400, 409, 500))
        );
    }

    // ---- Tracing ----

    public static ChainBuilder traceForward() {
        return exec(session -> {
            var ids = LoadTestDataSeeder.batchIds();
            String id = ids.get(ThreadLocalRandom.current().nextInt(ids.size()));
            return session.set("traceBatchId", id);
        }).exec(
                http("Charge vorwärts tracen")
                        .get("/api/production/batches/#{traceBatchId}/trace-forward")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().in(200, 404))
        );
    }

    public static ChainBuilder traceBackward() {
        return exec(session -> {
            var ids = LoadTestDataSeeder.batchIds();
            String id = ids.get(ThreadLocalRandom.current().nextInt(ids.size()));
            return session.set("traceBatchId", id);
        }).exec(
                http("Charge rückwärts tracen")
                        .get("/api/production/batches/#{traceBatchId}/trace-backward")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().in(200, 404))
        );
    }

    // ---- Produktionsaufträge ----

    public static ChainBuilder createProductionOrder() {
        return exec(session -> {
            var ids = LoadTestDataSeeder.recipeIds();
            String recipeId = ids.get(ThreadLocalRandom.current().nextInt(ids.size()));
            return session.set("prodOrderBody", JsonBodyBuilder.createProductionOrderBody(recipeId));
        }).exec(
                http("Produktionsauftrag anlegen")
                        .post("/api/production/production-orders")
                        .header("Authorization", "Bearer #{accessToken}")
                        .header("Content-Type", "application/json")
                        .body(StringBody("#{prodOrderBody}"))
                        .check(status().in(200, 201, 400, 409, 500))
                        .check(jsonPath("$.id").optional().saveAs("productionOrderId"))
        );
    }

    public static ChainBuilder releaseProductionOrder() {
        return exec(
                http("Produktionsauftrag freigeben")
                        .post("/api/production/production-orders/#{productionOrderId}/release")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().in(200, 400, 409, 500))
        );
    }

    public static ChainBuilder startOrderProduction() {
        return exec(
                http("Produktionsauftrag starten")
                        .post("/api/production/production-orders/#{productionOrderId}/start")
                        .header("Authorization", "Bearer #{accessToken}")
                        .header("Content-Type", "application/json")
                        .body(StringBody("{\"batchId\":\"#{orderBatchId}\"}"))
                        .check(status().in(200, 400, 409, 500))
        );
    }

    public static ChainBuilder completeProductionOrder() {
        return exec(
                http("Produktionsauftrag abschließen")
                        .post("/api/production/production-orders/#{productionOrderId}/complete")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().in(200, 400, 409, 500))
        );
    }

    public static ChainBuilder cancelProductionOrder() {
        return exec(
                http("Produktionsauftrag stornieren")
                        .post("/api/production/production-orders/#{productionOrderId}/cancel")
                        .header("Authorization", "Bearer #{accessToken}")
                        .header("Content-Type", "application/json")
                        .body(StringBody(JsonBodyBuilder.cancelProductionOrderBody()))
                        .check(status().in(200, 400, 409, 500))
        );
    }

    public static ChainBuilder rescheduleProductionOrder() {
        return exec(
                http("Produktionsauftrag umterminieren")
                        .post("/api/production/production-orders/#{productionOrderId}/reschedule")
                        .header("Authorization", "Bearer #{accessToken}")
                        .header("Content-Type", "application/json")
                        .body(StringBody(JsonBodyBuilder.rescheduleProductionOrderBody()))
                        .check(status().in(200, 400, 409, 500))
        );
    }

    public static ChainBuilder listProductionOrders() {
        return exec(
                http("Produktionsaufträge auflisten")
                        .get("/api/production/production-orders")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().is(200))
        );
    }

    public static ChainBuilder listProductionOrdersByStatus() {
        return exec(
                http("Produktionsaufträge nach Status")
                        .get("/api/production/production-orders?status=PLANNED")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().is(200))
        );
    }

    // ---- Zusammengesetztes Szenario ----

    /**
     * Produktions-Workflow: Rezepte lesen → Charge planen → Auftrag anlegen → freigeben → mit Charge starten.
     */
    public static ScenarioBuilder productionWorkflow() {
        return scenario("Produktions-Workflow")
                .exec(AuthenticationScenario.login("admin", "admin123"))
                .exec(listRecipes())
                .pause(1, 2)
                .exec(getRandomRecipe())
                .pause(1, 2)
                // Charge planen für Order-Start (separate Batch, wird nicht direkt gestartet)
                .exec(planBatch())
                .pause(1, 2)
                // Fallback: Seeded Batch-ID nutzen, falls planBatch fehlschlug
                .exec(session -> {
                    if (!session.contains("batchId")) {
                        var ids = LoadTestDataSeeder.batchIds();
                        if (ids != null && !ids.isEmpty()) {
                            return session.set("batchId", ids.get(ThreadLocalRandom.current().nextInt(ids.size())));
                        }
                    }
                    return session;
                })
                // Produktionsauftrag anlegen, freigeben und mit Charge starten
                .exec(createProductionOrder())
                .pause(1, 2)
                // Fallback: Seeded ProductionOrder-ID nutzen, falls createProductionOrder fehlschlug
                .exec(session -> {
                    if (!session.contains("productionOrderId")) {
                        var ids = LoadTestDataSeeder.productionOrderIds();
                        if (ids != null && !ids.isEmpty()) {
                            return session.set("productionOrderId", ids.get(ThreadLocalRandom.current().nextInt(ids.size())));
                        }
                    }
                    return session;
                })
                .doIf(session -> session.contains("productionOrderId") && session.contains("batchId")).then(
                        exec(releaseProductionOrder())
                                .pause(1, 2)
                                .exec(session -> session.set("orderBatchId", session.getString("batchId")))
                                .exec(startOrderProduction())
                )
                .pause(1, 2)
                // Separate Charge planen → starten → abschließen (unabhängiger Workflow)
                .exec(planBatch())
                .pause(1, 2)
                // Fallback: Seeded Batch-ID nutzen
                .exec(session -> {
                    if (!session.contains("batchId")) {
                        var ids = LoadTestDataSeeder.batchIds();
                        if (ids != null && !ids.isEmpty()) {
                            return session.set("batchId", ids.get(ThreadLocalRandom.current().nextInt(ids.size())));
                        }
                    }
                    return session;
                })
                .doIf(session -> session.contains("batchId")).then(
                        exec(startBatch())
                                .pause(2, 5)
                                .exec(completeBatch())
                )
                .pause(1, 2)
                // Produktionsauftrag abschließen (nutzt den zuvor gestarteten Order)
                .doIf(session -> session.contains("productionOrderId")).then(
                        exec(completeProductionOrder())
                )
                .pause(1, 2)
                // Neuen Auftrag anlegen, umterminieren und dann stornieren
                .exec(createProductionOrder())
                .pause(1, 2)
                .doIf(session -> session.contains("productionOrderId")).then(
                        exec(rescheduleProductionOrder())
                                .pause(1, 2)
                                .exec(cancelProductionOrder())
                )
                .pause(1, 2)
                // Produktionsaufträge auflisten (ungefiltert + nach Status)
                .exec(listProductionOrders())
                .pause(1, 2)
                .exec(listProductionOrdersByStatus())
                .pause(1, 2)
                // Tracing-Szenarien
                .exec(traceForward())
                .pause(1, 2)
                .exec(traceBackward())
                .pause(1, 2)
                // Nochmal Chargen-Liste prüfen
                .exec(listBatches());
    }

    /**
     * Read-Only-Produktions-Szenario für lesende Benutzer.
     */
    public static ScenarioBuilder productionReadOnly() {
        return scenario("Produktion Read-Only")
                .exec(AuthenticationScenario.login("admin", "admin123"))
                .repeat(15).on(
                        randomSwitch().on(
                                percent(25.0).then(listRecipes()),
                                percent(15.0).then(getRandomRecipe()),
                                percent(15.0).then(listBatches()),
                                percent(10.0).then(listProductionOrders()),
                                percent(10.0).then(listProductionOrdersByStatus()),
                                percent(12.5).then(traceForward()),
                                percent(12.5).then(traceBackward())
                        ).pause(1, 3)
                );
    }
}
