package de.effigenix.loadtest.scenario;

import de.effigenix.loadtest.infrastructure.LoadTestDataSeeder;
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.*;
import static io.gatling.javaapi.core.CoreDsl.StringBody;

/**
 * Lagerverwaltungs-Szenario: Lagerorte und Bestände verwalten.
 */
public final class InventoryScenario {

    private InventoryScenario() {}

    public static ChainBuilder listStorageLocations() {
        return exec(
                http("Lagerorte auflisten")
                        .get("/api/inventory/storage-locations")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().is(200))
        );
    }

    public static ChainBuilder getRandomStorageLocation() {
        return exec(session -> {
            var ids = LoadTestDataSeeder.storageLocationIds();
            String id = ids.get(ThreadLocalRandom.current().nextInt(ids.size()));
            return session.set("storageLocationId", id);
        }).exec(
                http("Lagerort laden")
                        .get("/api/inventory/storage-locations/#{storageLocationId}")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().is(200))
        );
    }

    public static ChainBuilder listStocks() {
        return exec(
                http("Bestände auflisten")
                        .get("/api/inventory/stocks")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().is(200))
        );
    }

    public static ChainBuilder listStocksBelowMinimum() {
        return exec(
                http("Bestände unter Minimum")
                        .get("/api/inventory/stocks/below-minimum")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().is(200))
        );
    }

    public static ChainBuilder listStocksByLocation() {
        return exec(session -> {
            var ids = LoadTestDataSeeder.storageLocationIds();
            String id = ids.get(ThreadLocalRandom.current().nextInt(ids.size()));
            return session.set("filterStorageLocationId", id);
        }).exec(
                http("Bestände nach Lagerort")
                        .get("/api/inventory/stocks?storageLocationId=#{filterStorageLocationId}")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().is(200))
        );
    }

    public static ChainBuilder listStockMovements() {
        return exec(
                http("Bestandsbewegungen auflisten")
                        .get("/api/inventory/stock-movements")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().is(200))
        );
    }

    public static ChainBuilder listStockMovementsByStock() {
        return exec(session -> {
            var ids = LoadTestDataSeeder.stockIds();
            if (ids == null || ids.isEmpty()) return session;
            String id = ids.get(ThreadLocalRandom.current().nextInt(ids.size()));
            return session.set("filterMovementStockId", id);
        }).exec(
                http("Bestandsbewegungen nach Bestand")
                        .get("/api/inventory/stock-movements?stockId=#{filterMovementStockId}")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().is(200))
        );
    }

    public static ChainBuilder listStockMovementsByBatch() {
        return exec(session -> {
            int idx = ThreadLocalRandom.current().nextInt(50);
            return session.set("filterBatchRef", "CHARGE-%05d".formatted(idx));
        }).exec(
                http("Bestandsbewegungen nach Charge")
                        .get("/api/inventory/stock-movements?batchReference=#{filterBatchRef}")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().is(200))
        );
    }

    public static ChainBuilder listStockMovementsByDateRange() {
        return exec(session -> {
            var rnd = ThreadLocalRandom.current();
            int startDay = rnd.nextInt(1, 28);
            int startMonth = rnd.nextInt(1, 13);
            int durationDays = rnd.nextInt(7, 90);
            var from = java.time.LocalDate.of(2026, startMonth, startDay)
                    .atStartOfDay(java.time.ZoneOffset.UTC).toInstant();
            var to = from.plus(java.time.Duration.ofDays(durationDays));
            return session
                    .set("dateFrom", from.toString())
                    .set("dateTo", to.toString());
        }).exec(
                http("Bestandsbewegungen nach Zeitraum")
                        .get("/api/inventory/stock-movements?from=#{dateFrom}&to=#{dateTo}")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().is(200))
        );
    }

    public static ChainBuilder reserveAndConfirmStock() {
        return exec(session -> {
            var stockIds = LoadTestDataSeeder.stockIds();
            if (stockIds == null || stockIds.isEmpty()) return session;
            String id = stockIds.get(ThreadLocalRandom.current().nextInt(stockIds.size()));
            return session
                    .set("reserveStockId", id)
                    .set("reserveRefId", "LT-PO-%06d".formatted(ThreadLocalRandom.current().nextInt(999999)));
        }).doIf(session -> session.contains("reserveStockId")).then(
                exec(
                        http("Reservierung anlegen")
                                .post("/api/inventory/stocks/#{reserveStockId}/reservations")
                                .header("Authorization", "Bearer #{accessToken}")
                                .body(StringBody("""
                                        {"referenceType":"PRODUCTION_ORDER","referenceId":"#{reserveRefId}","quantityAmount":"1","quantityUnit":"KILOGRAM","priority":"NORMAL"}"""))
                                .check(status().in(201, 409))
                                .check(jsonPath("$.id").optional().saveAs("reservationId"))
                ).doIf(session -> session.contains("reservationId")).then(
                        exec(
                                http("Reservierung bestätigen")
                                        .post("/api/inventory/stocks/#{reserveStockId}/reservations/#{reservationId}/confirm")
                                        .header("Authorization", "Bearer #{accessToken}")
                                        .check(status().in(204, 404))
                        ).exec(session -> session.remove("reservationId"))
                )
        );
    }

    public static ChainBuilder listInventoryCounts() {
        return exec(
                http("Inventuren auflisten")
                        .get("/api/inventory/inventory-counts")
                        .header("Authorization", "Bearer #{accessToken}")
                        .check(status().is(200))
        );
    }

    public static ChainBuilder getRandomInventoryCount() {
        return exec(session -> {
            var ids = LoadTestDataSeeder.inventoryCountIds();
            if (ids == null || ids.isEmpty()) return session;
            String id = ids.get(ThreadLocalRandom.current().nextInt(ids.size()));
            return session.set("inventoryCountId", id);
        }).doIf(session -> session.contains("inventoryCountId")).then(
                exec(
                        http("Inventur laden")
                                .get("/api/inventory/inventory-counts/#{inventoryCountId}")
                                .header("Authorization", "Bearer #{accessToken}")
                                .check(status().in(200, 404))
                )
        );
    }

    public static ChainBuilder createAndStartInventoryCount() {
        return exec(session -> {
            var ids = LoadTestDataSeeder.storageLocationIds();
            String id = ids.get(ThreadLocalRandom.current().nextInt(ids.size()));
            return session.set("icStorageLocationId", id);
        }).exec(
                http("Inventur anlegen")
                        .post("/api/inventory/inventory-counts")
                        .header("Authorization", "Bearer #{accessToken}")
                        .body(StringBody("""
                                {"storageLocationId":"#{icStorageLocationId}","countDate":"%s"}"""
                                .formatted(java.time.LocalDate.now())))
                        .check(status().in(201, 409))
                        .check(jsonPath("$.id").optional().saveAs("newInventoryCountId"))
        ).doIf(session -> session.contains("newInventoryCountId")).then(
                exec(
                        http("Inventur starten")
                                .patch("/api/inventory/inventory-counts/#{newInventoryCountId}/start")
                                .header("Authorization", "Bearer #{accessToken}")
                                .check(status().in(200, 400))
                ).exec(session -> session.remove("newInventoryCountId"))
        );
    }

    public static ChainBuilder recordCountItem() {
        return exec(session -> {
            var ids = LoadTestDataSeeder.inventoryCountIds();
            if (ids == null || ids.isEmpty()) return session;
            String id = ids.get(ThreadLocalRandom.current().nextInt(ids.size()));
            return session.set("recordCountId", id);
        }).doIf(session -> session.contains("recordCountId")).then(
                exec(
                        http("Inventur laden für Zählung")
                                .get("/api/inventory/inventory-counts/#{recordCountId}")
                                .header("Authorization", "Bearer #{accessToken}")
                                .check(status().in(200, 404))
                                .check(jsonPath("$.countItems[0].id").optional().saveAs("recordItemId"))
                ).doIf(session -> session.contains("recordItemId")).then(
                        exec(session -> {
                            int qty = ThreadLocalRandom.current().nextInt(0, 50);
                            return session.set("recordQty", "%d.0".formatted(qty));
                        }).exec(
                                http("Ist-Menge erfassen")
                                        .patch("/api/inventory/inventory-counts/#{recordCountId}/items/#{recordItemId}")
                                        .header("Authorization", "Bearer #{accessToken}")
                                        .body(StringBody("""
                                                {"actualQuantityAmount":"#{recordQty}","actualQuantityUnit":"KILOGRAM"}"""))
                                        .check(status().in(200, 400))
                        ).exec(session -> session.remove("recordItemId"))
                ).exec(session -> session.remove("recordCountId"))
        );
    }

    public static ChainBuilder recordStockMovement() {
        return exec(session -> {
            var rnd = ThreadLocalRandom.current();
            var stockIds = LoadTestDataSeeder.stockIds();
            var articleIds = LoadTestDataSeeder.articleIds();
            var stockBatchIds = LoadTestDataSeeder.stockBatchIds();
            if (stockIds == null || stockIds.isEmpty()
                    || stockBatchIds == null || stockBatchIds.isEmpty()) return session;

            int idx = rnd.nextInt(stockIds.size());
            return session
                    .set("mvStockId", stockIds.get(idx))
                    .set("mvArticleId", articleIds.get(idx % articleIds.size()))
                    .set("mvStockBatchId", stockBatchIds.get(rnd.nextInt(stockBatchIds.size())))
                    .set("mvBatchRef", "LT-CHARGE-%06d".formatted(rnd.nextInt(999999)))
                    .set("mvQty", "%d.0".formatted(rnd.nextInt(1, 30)));
        }).doIf(session -> session.contains("mvStockId")).then(
                exec(
                        http("Bestandsbewegung erfassen")
                                .post("/api/inventory/stock-movements")
                                .header("Authorization", "Bearer #{accessToken}")
                                .body(StringBody("""
                                        {"stockId":"#{mvStockId}","articleId":"#{mvArticleId}","stockBatchId":"#{mvStockBatchId}","batchId":"#{mvBatchRef}","batchType":"PRODUCED","movementType":"GOODS_RECEIPT","quantityAmount":"#{mvQty}","quantityUnit":"KILOGRAM"}"""))
                                .check(status().is(201))
                )
        );
    }

    /**
     * Lagerverwaltungs-Workflow: Überwiegend Lese-Operationen mit Bestandsbewegungen.
     */
    public static ScenarioBuilder inventoryWorkflow() {
        return scenario("Lagerverwaltung")
                .exec(AuthenticationScenario.login("admin", "admin123"))
                .repeat(15).on(
                        randomSwitch().on(
                                percent(15.0).then(listStocks()),
                                percent(10.0).then(listStorageLocations()),
                                percent(10.0).then(getRandomStorageLocation()),
                                percent(10.0).then(listStocksByLocation()),
                                percent(6.0).then(listStocksBelowMinimum()),
                                percent(6.0).then(listStockMovements()),
                                percent(4.0).then(listStockMovementsByStock()),
                                percent(4.0).then(listStockMovementsByBatch()),
                                percent(4.0).then(listStockMovementsByDateRange()),
                                percent(7.0).then(reserveAndConfirmStock()),
                                percent(6.0).then(recordStockMovement()),
                                percent(5.0).then(listInventoryCounts()),
                                percent(5.0).then(getRandomInventoryCount()),
                                percent(4.0).then(createAndStartInventoryCount()),
                                percent(4.0).then(recordCountItem())
                        ).pause(1, 3)
                );
    }
}
