package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers;

import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.TowerdefenseRoom;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.*;
import net.minestom.server.entity.attribute.Attribute;
import net.minestom.server.entity.damage.DamageType;
import net.minestom.server.timer.Task;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.stream.Stream;

public abstract class Tower extends EntityCreature {
    public enum Priority {
        FIRST(Type.PATH_DISTANCE),
        LAST(Type.PATH_DISTANCE),
        STRONG(Type.HEALTH),
        WEAK(Type.HEALTH),
        CLOSE(Type.TOWER_DISTANCE),
        FAR(Type.TOWER_DISTANCE);

        final Type type;
        Priority(Type type) {
            this.type = type;
        }

        public Type getType() {
            return this.type;
        }

        public enum Type {
            PATH_DISTANCE,
            HEALTH,
            TOWER_DISTANCE
        }
    }

    private final static int damageDivider = 10;
    private Priority priority = Priority.FIRST;
    protected float damage;
    protected int range;
    protected double attacksPerSecond;
    protected TaskSchedule attackDelay;
    private @Nullable Task attackTask;
    private float sellingPriceMultiplier = 1;

    public Tower(@NotNull EntityType entityType, int damage, double attacksPerSecond, int range) {
        super(entityType);
        this.damage = (float) damage / damageDivider;
        this.range = range;
        this.attacksPerSecond = attacksPerSecond;
        this.attackDelay = TaskSchedule.millis((long) (1000/attacksPerSecond));
    }

    public void startShooting() {
        this.attackTask = MinecraftServer.getSchedulerManager().scheduleTask(() -> {
            EntityCreature nextEnemy = this.getNextEnemy();
            if(nextEnemy == null) return this.attackDelay;
            this.lookAt(nextEnemy);
            this.attack(nextEnemy);
            return this.attackDelay;
        }, TaskSchedule.immediate());
    }

    @Override
    protected void remove(boolean permanent) {
        if(this.attackTask != null) this.attackTask.cancel();
        super.remove(permanent);
    }

    private @Nullable EntityCreature getNextEnemy() {
        List<EntityCreature> enemies = this.getSortedEnemies();
        if(enemies.isEmpty()) return null;

        return switch (this.priority) {
            case LAST, STRONG, FAR -> enemies.getLast();
            case FIRST, WEAK, CLOSE -> enemies.getFirst();
        };
    }

    private List<EntityCreature> getSortedEnemies() {
        Stream<EntityCreature> enemyStream = this.getRoomInstance().getEnemies().stream().parallel()
            .filter(enemy -> !enemy.isDead())
            .filter(enemy -> enemy.getPosition().distance(this.getPosition()) <= this.range);
        return switch (this.priority.getType()) {
            case PATH_DISTANCE -> enemyStream
                .sorted((o1, o2) -> {
                    Pos endPoint = this.getRoomInstance().getGame().getMazePath().getLast();
                    return Double.compare(
                        o1.getPosition().distance(endPoint),
                        o2.getPosition().distance(endPoint)
                    );
                }).toList();
            case TOWER_DISTANCE -> enemyStream
                .sorted((o1, o2) -> Double.compare(
                    o1.getPosition().distance(this.getPosition()),
                    o2.getPosition().distance(this.getPosition())
                )).toList();
            case HEALTH -> enemyStream
                .sorted((o1, o2) -> Float.compare(o1.getHealth(), o2.getHealth()))
                .toList();
        };
    }

    protected TowerdefenseRoom getRoomInstance() {
        return (TowerdefenseRoom) this.getInstance();
    }

    public int getSellingPrice(int buyPrice) {
        return (int) (this.sellingPriceMultiplier * buyPrice);
    }

    public double getValue() {
        return this.damage * this.attacksPerSecond * this.range * 2;
    }

    public int getRange() {
        return this.range;
    }

    protected void causeDamage(EntityCreature enemy) {
        this.causeDamage(enemy, this.damage);
    }

    protected void causeDamage(EntityCreature enemy, float amount) {
        enemy.damage(DamageType.PLAYER_ATTACK, amount);
        if(enemy.isDead()) this.getRoomInstance().addMoney((int) enemy.getAttribute(Attribute.MAX_HEALTH).getBaseValue());
    }

    protected abstract void attack(EntityCreature enemy);
}
