package com.firefrostgaming.bitchbot;

import net.minecraft.core.BlockPos;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.GameRules;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * Sequenced runner for everything Bitch Bot does on a successful first boot.
 *
 * Each step is independently try/catch'd so a single failure (bad URL, bad NBT,
 * etc.) does NOT take down the server or skip the rest of the pipeline. Steps
 * report their own success/failure to the log, and Provisioner tracks an
 * overall success flag that gates self-destruct.
 *
 * Steps:
 *   1. Locate provision.json
 *   2. Capture original level.dat spawn coords (used for command-block /tp baking)
 *   3. Download + verify schematic
 *   4. Paste schematic at spawn_coords
 *   5. Place 4 command blocks with /tp NBT
 *   6. Place rules signs (if provided)
 *   7. Set worldspawn = spawn_coords + worldspawn_offset
 *   8. Set gamerule doFireTick false
 *   9. Drop YAWP region config file (if provided)
 *  10. Self-destruct (rename jar) if all of the above succeeded and the flag is set
 */
public class Provisioner {

    private final MinecraftServer server;
    private final ServerLevel overworld;
    private final Path serverRoot;

    private boolean schematicOk    = false;
    private boolean commandBlocksOk = false;
    private boolean signsOk        = true;   // optional — start true
    private boolean worldspawnOk   = false;
    private boolean gamerulesOk    = false;
    private boolean yawpOk         = true;   // optional — start true

    public Provisioner(MinecraftServer server) {
        this.server = server;
        this.overworld = server.overworld();
        this.serverRoot = Paths.get("").toAbsolutePath();
    }

    public void run() {
        Path provisionPath = serverRoot.resolve("provision.json");
        ProvisionConfig cfg = ProvisionConfig.loadOrNull(provisionPath);
        if (cfg == null) {
            BitchBot.LOGGER.info("[BitchBot] No provision.json — dormant.");
            return;
        }
        BitchBot.LOGGER.info("[BitchBot] provision.json detected (version={}). Beginning provisioning.", cfg.version);

        // Step 1: capture the original level.dat spawn so command blocks can /tp back to it
        BlockPos originalSpawn = overworld.getSharedSpawnPos();
        BitchBot.LOGGER.info("[BitchBot] Original spawn: {} {} {}", originalSpawn.getX(), originalSpawn.getY(), originalSpawn.getZ());

        // Step 2-4: schematic
        try {
            byte[] data = SchematicLoader.downloadAndVerify(cfg.schematic_url, cfg.schematic_hash);
            if (data == null) throw new RuntimeException("schematic download/verification failed");
            BlockPos pasteAt = new BlockPos(cfg.spawn_coords.x, cfg.spawn_coords.y, cfg.spawn_coords.z);
            int placed = SchematicLoader.pasteAt(overworld, data, pasteAt, "center".equalsIgnoreCase(cfg.paste_origin));
            BitchBot.LOGGER.info("[BitchBot] Schematic pasted: {} blocks", placed);
            schematicOk = true;
        } catch (Throwable t) {
            BitchBot.LOGGER.error("[BitchBot] Schematic step failed: {}", t.getMessage());
        }

        // Step 5: command blocks (always attempted, even if schematic failed — staff can still use TP pads)
        try {
            CommandBlockPlacer.placeAll(overworld, cfg.command_blocks, originalSpawn);
            commandBlocksOk = true;
        } catch (Throwable t) {
            BitchBot.LOGGER.error("[BitchBot] Command block step failed: {}", t.getMessage());
        }

        // Step 6: rules signs (optional)
        if (cfg.rules_signs != null && !cfg.rules_signs.isEmpty()) {
            try {
                SignPlacer.placeAll(overworld, cfg.rules_signs);
            } catch (Throwable t) {
                BitchBot.LOGGER.error("[BitchBot] Sign step failed: {}", t.getMessage());
                signsOk = false;
            }
        }

        // Step 7: worldspawn
        try {
            int wx = cfg.spawn_coords.x + (cfg.worldspawn_offset != null ? cfg.worldspawn_offset.x : 0);
            int wy = cfg.spawn_coords.y + (cfg.worldspawn_offset != null ? cfg.worldspawn_offset.y : 0);
            int wz = cfg.spawn_coords.z + (cfg.worldspawn_offset != null ? cfg.worldspawn_offset.z : 0);
            overworld.setDefaultSpawnPos(new BlockPos(wx, wy, wz), 0.0F);
            BitchBot.LOGGER.info("[BitchBot] World spawn set to {} {} {}", wx, wy, wz);
            worldspawnOk = true;
        } catch (Throwable t) {
            BitchBot.LOGGER.error("[BitchBot] World spawn step failed: {}", t.getMessage());
        }

        // Step 8: gamerule doFireTick false
        try {
            server.getGameRules().getRule(GameRules.RULE_DOFIRETICK).set(false, server);
            BitchBot.LOGGER.info("[BitchBot] doFireTick → false");
            gamerulesOk = true;
        } catch (Throwable t) {
            BitchBot.LOGGER.error("[BitchBot] Gamerule step failed: {}", t.getMessage());
        }

        // Step 9: YAWP region file
        if (cfg.yawp_region != null) {
            try {
                YawpConfigWriter.write(serverRoot, cfg.yawp_region, overworld.dimension().location().toString());
            } catch (Throwable t) {
                BitchBot.LOGGER.error("[BitchBot] YAWP step failed: {}", t.getMessage());
                yawpOk = false;
            }
        }

        // Step 10: self-destruct
        boolean overallOk = schematicOk && commandBlocksOk && signsOk && worldspawnOk && gamerulesOk && yawpOk;
        BitchBot.LOGGER.info("[BitchBot] Provisioning result: schematic={} cmdblocks={} signs={} worldspawn={} gamerules={} yawp={}",
                schematicOk, commandBlocksOk, signsOk, worldspawnOk, gamerulesOk, yawpOk);

        if (overallOk && cfg.shouldSelfDestruct()) {
            try {
                SelfDestruct.disableOurJar();
            } catch (Throwable t) {
                BitchBot.LOGGER.error("[BitchBot] Self-destruct failed (manual cleanup needed): {}", t.getMessage());
            }
        } else if (!overallOk) {
            BitchBot.LOGGER.warn("[BitchBot] One or more steps failed — leaving jar enabled for retry on next boot.");
        }

        // Move provision.json out of the way so it doesn't re-trigger if jar isn't disabled
        try {
            Files.move(provisionPath, serverRoot.resolve("provision.json.applied"));
        } catch (Throwable ignored) {}
    }
}
