package net.nuggetmc.tplus.bot;

import com.mojang.authlib.GameProfile;
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.npc.NPC;
import net.citizensnpcs.trait.SkinTrait;
import net.nuggetmc.tplus.TerminatorPlus;
import net.nuggetmc.tplus.api.Terminator;
import net.nuggetmc.tplus.api.agent.Agent;
import net.nuggetmc.tplus.api.agent.legacyagent.LegacyMats;
import net.nuggetmc.tplus.api.agent.legacyagent.ai.NeuralNetwork;
import net.nuggetmc.tplus.api.event.BotDamageByPlayerEvent;
import net.nuggetmc.tplus.api.event.BotFallDamageEvent;
import net.nuggetmc.tplus.api.event.BotKilledByPlayerEvent;
import net.nuggetmc.tplus.api.utils.*;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.Waterlogged;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Damageable;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector;

import java.util.*;

public class CitizensNPC implements Terminator {
    
    private final NPC npc;
    private final TerminatorPlus plugin;
    private final BukkitScheduler scheduler;
    private final Agent agent;
    private final Vector offset;
    
    public ItemStack defaultItem;
    private NeuralNetwork network;
    private boolean shield;
    private boolean blocking;
    private boolean blockUse;
    private Vector velocity;
    private Vector oldVelocity;
    private boolean removeOnDeath;
    private int aliveTicks;
    private int kills;
    private byte groundTicks;
    private byte jumpTicks;
    private byte noFallTicks;
    private List<Block> standingOn = new ArrayList<>();
    private UUID targetPlayer = null;
    
    private CitizensNPC(NPC npc, Agent agent) {
        this.npc = npc;
        this.plugin = TerminatorPlus.getInstance();
        this.scheduler = Bukkit.getScheduler();
        this.agent = agent;
        this.defaultItem = new ItemStack(Material.AIR);
        this.velocity = new Vector(0, 0, 0);
        this.oldVelocity = velocity.clone();
        this.noFallTicks = 60;
        this.removeOnDeath = true;
        this.offset = MathUtils.circleOffset(3);
    }
    
    public static CitizensNPC createNPC(Location loc, String name, String skin) {
        // Create the NPC with Citizens API
        NPC npc = CitizensAPI.getNPCRegistry().createNPC(org.bukkit.entity.EntityType.PLAYER, ChatUtils.trim16(name));
        
        // Set location
        npc.spawn(loc);

        // Set attributes
        npc.data().setPersistent(NPC.Metadata.COLLIDABLE, true);
        npc.data().setPersistent(NPC.Metadata.DAMAGE_OTHERS, true);
        npc.data().setPersistent(NPC.Metadata.DEFAULT_PROTECTED, false);
        npc.data().setPersistent(NPC.Metadata.DROPS_ITEMS, true);
        npc.data().setPersistent(NPC.Metadata.FLUID_PUSHABLE, true);
        npc.data().setPersistent(NPC.Metadata.KEEP_CHUNK_LOADED, true);
        npc.data().setPersistent(NPC.Metadata.KNOCKBACK, true);
        npc.data().setPersistent(NPC.Metadata.PICKUP_ITEMS, true);
        npc.data().setPersistent(NPC.Metadata.PATHFINDER_OPEN_DOORS, true);
        
        // Create wrapper first so we can attach the trait
        CitizensNPC citizensNPC = new CitizensNPC(npc, TerminatorPlus.getInstance().getManager().getAgent());
        
        // Add the behavior trait for ticking (if the trait system is available)
        try {
            npc.addTrait(net.nuggetmc.tplus.bot.trait.BotBehaviorTrait.class);
            net.nuggetmc.tplus.bot.trait.BotBehaviorTrait trait = npc.getTrait(net.nuggetmc.tplus.bot.trait.BotBehaviorTrait.class);
            if (trait != null) {
                trait.setBot(citizensNPC);
            }
        } catch (Exception e) {
            // Trait might not be registered, continue anyway
        }
        
        // Apply skin if provided
        try {
            SkinTrait skinTrait = npc.getTrait(SkinTrait.class);
            if (skinTrait != null) {
                skinTrait.setSkinName(skin, false);
                /*if (skin != null && skin.length >= 2) {
                    skinTrait.setSkinPersistent(name, skin[0], skin[1]);
                } else {
                    // Try to fetch skin from Mojang API
                    String[] fetchedSkin = MojangAPI.getSkin(name);
                    if (fetchedSkin != null && fetchedSkin.length >= 2) {
                        skinTrait.setSkinPersistent(name, fetchedSkin[0], fetchedSkin[1]);
                    }
                }*/
            }
        } catch (Exception e) {
            Bukkit.getServer().getLogger().warning("Failed to get skin: "+e.getMessage()+Arrays.toString(e.getStackTrace()));
        }
        
        // Register with bot manager
        TerminatorPlus.getInstance().getManager().add(citizensNPC);
        
        return citizensNPC;
    }
    
    @Override
    public ItemStack getDefaultItem() {
        return defaultItem;
    }

    public static CitizensNPC createNPC(Location loc, String name) {
        return createNPC(loc, name, name);
    }
    
    @Override
    public String getBotName() {
        return npc.getName();
    }
    
    @Override
    public int getEntityId() {
        return npc.getEntity() != null ? npc.getEntity().getEntityId() : -1;
    }
    
    @Override
    public GameProfile getGameProfile() {
        // Citizens doesn't directly expose GameProfile, return null
        // This would be used for NMS operations which we're avoiding
        return null;
    }
    
    @Override
    public LivingEntity getBukkitEntity() {
        return (LivingEntity) npc.getEntity();
    }
    
    @Override
    public NeuralNetwork getNeuralNetwork() {
        return network;
    }
    
    @Override
    public void setNeuralNetwork(NeuralNetwork network) {
        this.network = network;
    }
    
    @Override
    public boolean hasNeuralNetwork() {
        return network != null;
    }
    
    @Override
    public Location getLocation() {
        return npc.getEntity() != null ? npc.getEntity().getLocation() : null;
    }
    
    @Override
    public BoundingBox getBotBoundingBox() {
        if (npc.getEntity() instanceof LivingEntity le) {
            return le.getBoundingBox();
        }
        return null;
    }
    
    @Override
    public boolean isBotAlive() {
        LivingEntity entity = getBukkitEntity();
        return entity != null && entity.isDead() == false;
    }
    
    @Override
    public float getBotHealth() {
        LivingEntity entity = getBukkitEntity();
        return entity != null ? (float) entity.getHealth() : 0;
    }
    
    @Override
    public float getBotMaxHealth() {
        LivingEntity entity = getBukkitEntity();
        return entity != null ? (float) entity.getMaxHealth() : 20;
    }
    
    @Override
    public boolean isBotOnFire() {
        LivingEntity entity = getBukkitEntity();
        return entity != null && entity.getFireTicks() > 0;
    }
    
    @Override
    public boolean isFalling() {
        return velocity.getY() < 0;
    }
    
    @Override
    public boolean isBotBlocking() {
        return blocking;
    }
    
    @Override
    public void block(int blockLength, int cooldown) {
        if (!shield || blockUse) return;
        startBlocking();
        scheduler.runTaskLater(plugin, () -> stopBlocking(cooldown), blockLength);
    }
    
    private void startBlocking() {
        this.blocking = true;
        this.blockUse = true;
    }
    
    private void stopBlocking(int cooldown) {
        this.blocking = false;
        scheduler.runTaskLater(plugin, () -> this.blockUse = false, cooldown);
    }
    
    @Override
    public boolean isBotInWater() {
        Location loc = getLocation();
        
        if (loc == null) return false;
        
        for (int i = 0; i <= 2; i++) {
            Material type = loc.getBlock().getType();
            
            if (type == Material.WATER || type == Material.LAVA) {
                return true;
            }
            
            loc.add(0, 0.9, 0);
        }
        
        return false;
    }
    
    @Override
    public boolean isBotOnGround() {
        return groundTicks != 0;
    }
    
    @Override
    public List<Block> getStandingOn() {
        return standingOn;
    }
    
    @Override
    public void setBotPitch(float pitch) {
        if (npc.getEntity() instanceof LivingEntity le) {
            le.setRotation(le.getYaw(), pitch);
        }
    }
    
    @Override
    public void jump(Vector velocity) {
        if (jumpTicks == 0 && groundTicks > 1) {
            jumpTicks = 4;
            this.velocity = velocity;
        }
    }
    
    @Override
    public void jump() {
        jump(new Vector(0, 0.42, 0));
    }
    
    @Override
    public void walk(Vector vel) {
        double max = 0.4;
        
        Vector sum = velocity.clone().add(vel);
        if (sum.length() > max) sum.normalize().multiply(max);
        
        velocity = sum;
    }
    
    @Override
    public void look(BlockFace face) {
        look(face.getDirection(), face == BlockFace.DOWN || face == BlockFace.UP);
    }
    
    private void look(Vector dir, boolean keepYaw) {
        float yaw, pitch;
        
        LivingEntity entity = getBukkitEntity();
        if (entity == null) return;
        
        if (keepYaw) {
            yaw = entity.getYaw();
            pitch = MathUtils.fetchPitch(dir);
        } else {
            float[] vals = MathUtils.fetchYawPitch(dir);
            yaw = vals[0];
            pitch = vals[1];
        }
        
        entity.setRotation(yaw, pitch);
    }
    
    @Override
    public void faceLocation(Location location) {
        LivingEntity entity = getBukkitEntity();
        if (entity != null) {
            look(location.toVector().subtract(entity.getLocation().toVector()), false);
        }
    }

    @Override
    public void attack(Entity target, double extra) {
        faceLocation(target.getLocation());
        punch();
        
        double damage = ItemUtils.getLegacyAttackDamage(defaultItem)+extra;
        
        if (target instanceof Damageable) {
            ((Damageable) target).damage(damage, getBukkitEntity());
        }
    }
    @Override
    public void attack(Entity target) {
        attack(target,0);
    }
    
    @Override
    public void attemptBlockPlace(Location loc, Material type, boolean down) {
        if (down) {
            look(BlockFace.DOWN);
        } else {
            faceLocation(loc);
        }
        
        setItem(new ItemStack(Material.COBBLESTONE));
        punch();
        
        Block block = loc.getBlock();
        World world = loc.getWorld();
        
        if (!LegacyMats.isSolid(block.getType())) {
            block.setType(type);
            if (world != null) world.playSound(loc, Sound.BLOCK_STONE_PLACE, SoundCategory.BLOCKS, 1, 1);
        }
    }
    
    @Override
    public void punch() {
        LivingEntity entity = getBukkitEntity();
        if (entity instanceof Player player) {
            // Swing the arm animation
            player.swingMainHand();
        }
    }
    
    @Override
    public void swim() {
        LivingEntity entity = getBukkitEntity();
        if (entity != null) {
            entity.setSwimming(true);
        }
    }
    
    @Override
    public void sneak() {
        LivingEntity entity = getBukkitEntity();
        if (entity instanceof Player player) {
            player.setSneaking(true);
        }
    }
    
    @Override
    public void stand() {
        LivingEntity entity = getBukkitEntity();
        if (entity instanceof Player player) {
            player.setSneaking(false);
            player.setSwimming(false);
        }
    }
    
    @Override
    public void addFriction(double factor) {
        double frictionMin = 0.01;
        
        double x = velocity.getX();
        double z = velocity.getZ();
        
        velocity.setX(Math.abs(x) < frictionMin ? 0 : x * factor);
        velocity.setZ(Math.abs(z) < frictionMin ? 0 : z * factor);
    }
    
    @Override
    public void removeVisually() {
        if (npc.isSpawned()) {
            npc.despawn();
        }
    }
    
    @Override
    public void removeBot() {
        removeVisually();
        try {
            CitizensAPI.getNPCRegistry().deregister(npc);
        } catch (Exception e) {
            // NPC might already be deregistered
        }
        TerminatorPlus.getInstance().getManager().remove(this);
    }
    
    @Override
    public int getKills() {
        return kills;
    }
    
    @Override
    public void incrementKills() {
        kills++;
    }
    
    @Override
    public void setItem(ItemStack item) {
        setItem(item, EquipmentSlot.HAND);
    }
    
    @Override
    public void setItem(ItemStack item, EquipmentSlot slot) {
        if (item == null) item = defaultItem;
        
        LivingEntity entity = getBukkitEntity();
        if (entity instanceof Player player) {
            if (slot == EquipmentSlot.HAND) {
                player.getInventory().setItemInMainHand(item);
            } else if (slot == EquipmentSlot.OFF_HAND) {
                player.getInventory().setItemInOffHand(item);
            } else if (slot == EquipmentSlot.HEAD) {
                player.getInventory().setHelmet(item);
            } else if (slot == EquipmentSlot.CHEST) {
                player.getInventory().setChestplate(item);
            } else if (slot == EquipmentSlot.LEGS) {
                player.getInventory().setLeggings(item);
            } else if (slot == EquipmentSlot.FEET) {
                player.getInventory().setBoots(item);
            }
        }
    }
    
    public void setShield(boolean enabled) {
        this.shield = enabled;
        
        LivingEntity entity = getBukkitEntity();
        if (entity instanceof Player player) {
            player.getInventory().setItemInOffHand(new ItemStack(enabled ? Material.SHIELD : Material.AIR));
        }
    }
    
    @Override
    public void setItemOffhand(ItemStack item) {
        setItem(item, EquipmentSlot.OFF_HAND);
    }
    
    @Override
    public void setDefaultItem(ItemStack item) {
        this.defaultItem = item;
    }
    
    @Override
    public UUID getTargetPlayer() {
        return targetPlayer;
    }
    
    @Override
    public void setTargetPlayer(UUID target) {
        this.targetPlayer = target;
    }
    
    @Override
    public Vector getVelocity() {
        return velocity.clone();
    }
    
    @Override
    public void setVelocity(Vector vector) {
        this.velocity = vector;
    }
    
    @Override
    public void addVelocity(Vector vector) {
        if (MathUtils.isNotFinite(vector)) {
            velocity = vector;
            return;
        }
        
        velocity.add(vector);
    }
    
    @Override
    public int getAliveTicks() {
        return aliveTicks;
    }
    
    @Override
    public int getNoFallTicks() {
        return noFallTicks;
    }
    
    @Override
    public boolean tickDelay(int i) {
        return aliveTicks % i == 0;
    }
    
    @Override
    public Vector getOffset() {
        return offset;
    }
    
    // Internal tracking methods (not from Terminator interface)
    
    public NPC getNPC() {
        return npc;
    }
    
    public void tick() {
        if (!isBotAlive()) {
            this.removeBot();
            return;
        }

        getBukkitEntity().setFallDistance(0);
            
        aliveTicks++;
        
        if (jumpTicks > 0) --jumpTicks;
        if (noFallTicks > 0) --noFallTicks;
        
        if (checkGround()) {
            if (groundTicks < 5) groundTicks++;
        } else {
            groundTicks = 0;
        }
        
        updateLocation();
        
        if (!isBotAlive()) return;
        
        float health = getBotHealth();
        float maxHealth = getBotMaxHealth();
        float regenAmount = 0.025f;
        float amount;
        
        if (health < maxHealth - regenAmount) {
            amount = health + regenAmount;
        } else {
            amount = maxHealth;
        }
        
        LivingEntity entity = getBukkitEntity();
        if (entity != null) {
            entity.setHealth(amount);
        }
        
        fallDamageCheck();
        
        oldVelocity = velocity.clone();
        
        doTick();
    }
    
    private void updateLocation() {
        double y;
        
        MathUtils.clean(velocity);
        
        if (isBotInWater()) {
            y = Math.min(velocity.getY() + 0.1, 0.1);
            addFriction(0.8);
            velocity.setY(y);
        } else {
            if (groundTicks != 0) {
                velocity.setY(0);
                addFriction(0.5);
                y = 0;
            } else {
                y = velocity.getY();
                if (jumpTicks - 3 <= 0) {
                    velocity.setY(Math.max(y - 0.08, -3.5));
                }
            }
        }
        
        LivingEntity entity = getBukkitEntity();
        if (entity != null) {
            entity.setVelocity(velocity.clone());
        }
    }
    
    private void doTick() {
        // Custom behavior can be implemented here or in a trait
    }
    
    private void fallDamageCheck() {
        if (groundTicks != 0 && noFallTicks == 0 && !(oldVelocity.getY() >= -0.8) && !isFallBlocked()) {
            BotFallDamageEvent event = new BotFallDamageEvent(this, new ArrayList<>(getStandingOn()));
            
            agent.onFallDamage(event);
            
            if (!event.isCancelled()) {
                Bukkit.getLogger().info("clutch failed");
                LivingEntity entity = getBukkitEntity();
                if (entity != null && entity instanceof Damageable damageable) {
                    float damage = (float) Math.pow(3.6, -oldVelocity.getY());
                    //damageable.damage(damage);
                }
            }
        }
    }
    
    private boolean isFallBlocked() {
        LivingEntity entity = getBukkitEntity();
        if (entity == null) return false;
        
        BoundingBox box = entity.getBoundingBox();
        double[] xVals = new double[]{
                box.getMinX(),
                box.getMaxX() - 0.01
        };
        
        double[] zVals = new double[]{
                box.getMinZ(),
                box.getMaxZ() - 0.01
        };
        BoundingBox playerBox = new BoundingBox(box.getMinX(), entity.getLocation().getY() - 0.01, box.getMinZ(),
                box.getMaxX(), entity.getLocation().getY() + entity.getHeight(), box.getMaxZ());
        for (double x : xVals) {
            for (double z : zVals) {
                Location loc = new Location(entity.getWorld(), Math.floor(x), entity.getLocation().getY(), Math.floor(z));
                Block block = loc.getBlock();
                if (block.getBlockData() instanceof Waterlogged wl && wl.isWaterlogged())
                    return true;
                if (BotUtils.NO_FALL.contains(loc.getBlock().getType()) && (BotUtils.overlaps(playerBox, loc.getBlock().getBoundingBox())
                        || loc.getBlock().getType() == Material.WATER || loc.getBlock().getType() == Material.LAVA))
                    return true;
            }
        }
        return false;
    }
    
    public boolean checkGround() {
        double vy = velocity.getY();
        
        if (vy > 0) {
            return false;
        }
        
        return checkStandingOn();
    }
    
    public boolean checkStandingOn() {
        LivingEntity entity = getBukkitEntity();
        if (entity == null) return false;
        
        World world = entity.getWorld();
        BoundingBox box = entity.getBoundingBox();
        
        double[] xVals = new double[]{
                box.getMinX(),
                box.getMaxX()
        };
        
        double[] zVals = new double[]{
                box.getMinZ(),
                box.getMaxZ()
        };
        BoundingBox playerBox = new BoundingBox(box.getMinX(), entity.getLocation().getY() - 0.01, box.getMinZ(),
                box.getMaxX(), entity.getLocation().getY() + entity.getHeight(), box.getMaxZ());
        List<Block> standingOn = new ArrayList<>();
        List<Location> locations = new ArrayList<>();
        
        for (double x : xVals) {
            for (double z : zVals) {
                Location loc = new Location(world, x, entity.getLocation().getY() - 0.01, z);
                Block block = world.getBlockAt(loc);
                
                if ((LegacyMats.isSolid(block.getType()) || LegacyMats.canStandOn(block.getType())) && BotUtils.overlaps(playerBox, block.getBoundingBox())) {
                    if (!locations.contains(block.getLocation())) {
                        standingOn.add(block);
                        locations.add(block.getLocation());
                    }
                }
            }
        }
        
        // Fence/wall check
        for (double x : xVals) {
            for (double z : zVals) {
                Location loc = new Location(world, x, entity.getLocation().getY() - 0.51, z);
                Block block = world.getBlockAt(loc);
                BoundingBox blockBox = loc.getBlock().getBoundingBox();
                BoundingBox modifiedBox = new BoundingBox(blockBox.getMinX(), blockBox.getMinY(), blockBox.getMinZ(), blockBox.getMaxX(),
                        blockBox.getMinY() + 1.5, blockBox.getMaxZ());
                
                if ((LegacyMats.FENCE.contains(block.getType()) || LegacyMats.GATES.contains(block.getType()))
                        && LegacyMats.isSolid(block.getType()) && BotUtils.overlaps(playerBox, modifiedBox)) {
                    if (!locations.contains(block.getLocation())) {
                        standingOn.add(block);
                        locations.add(block.getLocation());
                    }
                }
            }
        }
        
        // Closest block comes first
        Collections.sort(standingOn, (a, b) ->
                Double.compare(BotUtils.getHorizSqDist(a.getLocation(), getLocation()), BotUtils.getHorizSqDist(b.getLocation(), getLocation())));
        
        this.standingOn = standingOn;
        return !standingOn.isEmpty();
    }
    
    public void setRemoveOnDeath(boolean enabled) {
        this.removeOnDeath = enabled;
    }
    
    public boolean shouldRemoveOnDeath() {
        return removeOnDeath;
    }
    
    public World.Environment getDimension() {
        LivingEntity entity = getBukkitEntity();
        if (entity != null) {
            return entity.getWorld().getEnvironment();
        }
        return null;
    }
    
    @Override
    public void renderBot(Object packetListener, boolean login) {
        // Citizens handles rendering automatically
        // No need for manual packet sending
    }
    
    @Override
    public boolean isInPlayerList() {
        // With Citizens, the NPC is always in the player list
        return true;
    }
}
