package fr.koka99cab.ibelievechickenfly.chickenfly;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.fabricmc.fabric.api.event.player.UseItemCallback;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.minecraft.core.Holder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.animal.chicken.Chicken;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;

public final class ChickenCarryController {
	private static final int EFFECT_DURATION_TICKS = 10;
	private static final int JUMP_BOOST_AMPLIFIER = 0;
	private static final int SLOW_FALLING_AMPLIFIER = 0;
	private static final double CHICKEN_HEIGHT_OFFSET = 0.5D;
	private static final double CHICKEN_FORWARD_OFFSET = 0.18D;
	private static final double CHICKEN_VELOCITY_COMPENSATION = 1.2D;
	private static final double RELEASE_DISTANCE = 0.9D;
	private static final double RELEASE_EXTRA_DISTANCE = 0.35D;
	private static final double RELEASE_SIDE_OFFSET = 0.55D;
	private static final double RELEASE_COLLISION_EPSILON = 1.0E-4D;
	private static final double[] RELEASE_Y_OFFSETS = new double[] {
		0.0D, 0.25D, 0.5D, 1.0D, 1.5D, 2.0D, -0.25D, -0.5D, -1.0D
	};

	private static final Map<UUID, UUID> PLAYER_TO_CHICKEN = new HashMap<>();
	private static final Map<UUID, UUID> CHICKEN_TO_PLAYER = new HashMap<>();

	private ChickenCarryController() {
	}

	public static void register() {
		UseItemCallback.EVENT.register(ChickenCarryController::tryReleaseChicken);
		UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> tryReleaseChicken(player, world, hand));
		ServerTickEvents.END_SERVER_TICK.register(server -> server.getPlayerList().getPlayers().forEach(ChickenCarryController::refreshChickenFlyState));
		ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> releaseChicken(handler.player, false));
	}

	public static InteractionResult tryPickUpChicken(Player player, Level level, InteractionHand hand, Entity entity) {
		if (player.isSpectator() || !player.isCrouching() || !(entity instanceof Chicken chicken)) {
			return InteractionResult.PASS;
		}

		if (!player.getItemInHand(hand).isEmpty() || !player.getPassengers().isEmpty() || chicken.isPassenger()) {
			return InteractionResult.PASS;
		}

		if (level.isClientSide()) {
			return InteractionResult.SUCCESS;
		}

		if (!(player instanceof ServerPlayer serverPlayer)) {
			return InteractionResult.PASS;
		}

		return pickUpChicken(serverPlayer, chicken) ? InteractionResult.SUCCESS_SERVER : InteractionResult.PASS;
	}

	private static InteractionResult tryReleaseChicken(Player player, Level level, InteractionHand hand) {
		if (player.isSpectator() || !player.isCrouching() || !player.getItemInHand(hand).isEmpty()) {
			return InteractionResult.PASS;
		}

		if (level.isClientSide()) {
			return InteractionResult.PASS;
		}

		Chicken chicken = getCarriedChicken(player);
		if (chicken == null) {
			return InteractionResult.PASS;
		}

		releaseChicken(player, true);
		return InteractionResult.SUCCESS_SERVER;
	}

	private static boolean pickUpChicken(ServerPlayer player, Chicken chicken) {
		if (PLAYER_TO_CHICKEN.containsKey(player.getUUID()) || CHICKEN_TO_PLAYER.containsKey(chicken.getUUID())) {
			return false;
		}

		PLAYER_TO_CHICKEN.put(player.getUUID(), chicken.getUUID());
		CHICKEN_TO_PLAYER.put(chicken.getUUID(), player.getUUID());
		chicken.stopRiding();
		chicken.setNoGravity(true);
		chicken.noPhysics = true;

		if (chicken instanceof Mob mob) {
			mob.setNoAi(true);
		}

		positionChicken(player, chicken);
		return true;
	}

	private static void refreshChickenFlyState(ServerPlayer player) {
		Chicken chicken = getCarriedChicken(player);
		if (chicken == null) {
			return;
		}

		if (!player.isAlive() || chicken.level() != player.level()) {
			releaseChicken(player, false);
			return;
		}

		positionChicken(player, chicken);
		refreshHiddenEffect(player, MobEffects.JUMP_BOOST, JUMP_BOOST_AMPLIFIER);
		refreshHiddenEffect(player, MobEffects.SLOW_FALLING, SLOW_FALLING_AMPLIFIER);
	}

	private static void refreshHiddenEffect(ServerPlayer player, Holder<MobEffect> effect, int amplifier) {
		MobEffectInstance currentEffect = player.getEffect(effect);
		if (currentEffect != null && currentEffect.getAmplifier() == amplifier && currentEffect.getDuration() > EFFECT_DURATION_TICKS / 2) {
			return;
		}

		player.addEffect(new MobEffectInstance(effect, EFFECT_DURATION_TICKS, amplifier, false, false, false));
	}

	private static void releaseChicken(Player player, boolean placeInFront) {
		Chicken chicken = getCarriedChicken(player);
		if (chicken == null) {
			clearChickenAssociation(player.getUUID(), null);
			return;
		}

		clearChickenAssociation(player.getUUID(), chicken.getUUID());
		chicken.setNoGravity(false);
		chicken.noPhysics = false;

		if (chicken instanceof Mob mob) {
			mob.setNoAi(false);
		}

		Vec3 releasePos = findSafeReleasePosition(player, chicken, placeInFront);

		chicken.setPos(releasePos.x, releasePos.y, releasePos.z);
		chicken.setYRot(player.getYRot());
		chicken.setXRot(0.0F);
		chicken.setYBodyRot(player.getYRot());
		chicken.setYHeadRot(player.getYRot());
		chicken.setDeltaMovement(Vec3.ZERO);
		chicken.resetFallDistance();
	}

	private static Vec3 findSafeReleasePosition(Player player, Chicken chicken, boolean placeInFront) {
		Level level = player.level();
		Vec3 playerPos = player.position();

		if (!placeInFront) {
			Vec3 safePlayerPos = findSafeStandingPosition(level, chicken, playerPos);
			return safePlayerPos != null ? safePlayerPos : playerPos.add(0.0D, player.getEyeHeight(), 0.0D);
		}

		Vec3 forward = getHorizontalLook(player);
		Vec3 right = new Vec3(-forward.z, 0.0D, forward.x);
		Vec3 forwardPos = playerPos.add(forward.scale(RELEASE_DISTANCE + RELEASE_EXTRA_DISTANCE));
		Vec3[] candidates = new Vec3[] {
			forwardPos,
			forwardPos.add(right.scale(RELEASE_SIDE_OFFSET)),
			forwardPos.add(right.scale(-RELEASE_SIDE_OFFSET)),
			playerPos.add(forward.scale(RELEASE_DISTANCE * 0.5D)),
			playerPos.add(right.scale(RELEASE_SIDE_OFFSET)),
			playerPos.add(right.scale(-RELEASE_SIDE_OFFSET))
		};

		for (Vec3 candidate : candidates) {
			Vec3 safeCandidate = findSafeStandingPosition(level, chicken, candidate);
			if (safeCandidate != null) {
				return safeCandidate;
			}
		}

		Vec3 headFallback = playerPos.add(0.0D, player.getEyeHeight() + CHICKEN_HEIGHT_OFFSET, 0.0D);
		Vec3 safeFallback = findSafeStandingPosition(level, chicken, headFallback);
		return safeFallback != null ? safeFallback : headFallback;
	}

	private static Vec3 getHorizontalLook(Player player) {
		Vec3 look = player.getLookAngle();
		Vec3 horizontalLook = new Vec3(look.x, 0.0D, look.z);
		if (horizontalLook.lengthSqr() > 1.0E-4D) {
			return horizontalLook.normalize();
		}

		double yawRadians = Math.toRadians(player.getYRot());
		return new Vec3(-Math.sin(yawRadians), 0.0D, Math.cos(yawRadians));
	}

	private static Vec3 findSafeStandingPosition(Level level, Chicken chicken, Vec3 basePosition) {
		for (double yOffset : RELEASE_Y_OFFSETS) {
			Vec3 candidate = basePosition.add(0.0D, yOffset, 0.0D);
			AABB boundingBox = chicken.getDimensions(Pose.STANDING)
				.makeBoundingBox(candidate)
				.deflate(RELEASE_COLLISION_EPSILON);
			if (level.noCollision(chicken, boundingBox)) {
				return candidate;
			}
		}

		return null;
	}

	private static void positionChicken(ServerPlayer player, Chicken chicken) {
		Vec3 look = player.getLookAngle();
		Vec3 forwardOffset = new Vec3(look.x, 0.0D, look.z).scale(CHICKEN_FORWARD_OFFSET);
		Vec3 velocityOffset = new Vec3(player.getDeltaMovement().x, 0.0D, player.getDeltaMovement().z).scale(CHICKEN_VELOCITY_COMPENSATION);
		Vec3 headPos = player.position()
			.add(0.0D, player.getEyeHeight() + CHICKEN_HEIGHT_OFFSET, 0.0D)
			.add(forwardOffset)
			.add(velocityOffset);

		chicken.setPos(headPos.x, headPos.y, headPos.z);
		chicken.setYRot(player.getYRot());
		chicken.setXRot(0.0F);
		chicken.setYBodyRot(player.getYRot());
		chicken.setYHeadRot(player.getYRot());
		chicken.setDeltaMovement(Vec3.ZERO);
	}

	private static Chicken getCarriedChicken(Player player) {
		UUID chickenUuid = PLAYER_TO_CHICKEN.get(player.getUUID());
		if (chickenUuid == null || player.level().getServer() == null) {
			return null;
		}

		for (ServerLevel level : player.level().getServer().getAllLevels()) {
			Entity entity = level.getEntity(chickenUuid);
			if (entity instanceof Chicken chicken && entity.isAlive()) {
				return chicken;
			}
		}

		clearChickenAssociation(player.getUUID(), chickenUuid);
		return null;
	}

	public static boolean isChickenCarried(Entity entity) {
		return entity instanceof Chicken && CHICKEN_TO_PLAYER.containsKey(entity.getUUID());
	}

	private static void clearChickenAssociation(UUID playerUuid, UUID chickenUuid) {
		UUID actualChickenUuid = chickenUuid != null ? chickenUuid : PLAYER_TO_CHICKEN.remove(playerUuid);
		if (actualChickenUuid != null) {
			PLAYER_TO_CHICKEN.remove(playerUuid);
			CHICKEN_TO_PLAYER.remove(actualChickenUuid);
		}
	}
}
