package ai

import (
	"testing"
	"time"

	"a301_game_server/internal/combat"
	"a301_game_server/internal/entity"
	"a301_game_server/pkg/mathutil"
	pb "a301_game_server/proto/gen/pb"
)

type mockPlayer struct {
	id    uint64
	pos   mathutil.Vec3
	hp    int32
	maxHP int32
	alive bool
}

func (m *mockPlayer) EntityID() uint64           { return m.id }
func (m *mockPlayer) EntityType() entity.Type    { return entity.TypePlayer }
func (m *mockPlayer) Position() mathutil.Vec3    { return m.pos }
func (m *mockPlayer) SetPosition(p mathutil.Vec3) { m.pos = p }
func (m *mockPlayer) Rotation() float32          { return 0 }
func (m *mockPlayer) SetRotation(float32)        {}
func (m *mockPlayer) ToProto() *pb.EntityState   { return &pb.EntityState{EntityId: m.id} }
func (m *mockPlayer) HP() int32                  { return m.hp }
func (m *mockPlayer) MaxHP() int32               { return m.maxHP }
func (m *mockPlayer) SetHP(hp int32)             { m.hp = hp; m.alive = hp > 0 }
func (m *mockPlayer) MP() int32                  { return 100 }
func (m *mockPlayer) SetMP(int32)                {}
func (m *mockPlayer) IsAlive() bool              { return m.alive }
func (m *mockPlayer) Stats() combat.CombatStats  { return combat.CombatStats{Str: 10, Dex: 10, Int: 10, Level: 1} }

type mockProvider struct {
	entities map[uint64]entity.Entity
}

func (p *mockProvider) GetEntity(id uint64) entity.Entity {
	return p.entities[id]
}

func (p *mockProvider) GetPlayersInRange(center mathutil.Vec3, radius float32) []entity.Entity {
	radiusSq := radius * radius
	var result []entity.Entity
	for _, e := range p.entities {
		if e.EntityType() == entity.TypePlayer {
			if e.Position().DistanceSqTo(center) <= radiusSq {
				result = append(result, e)
			}
		}
	}
	return result
}

type mockSkillUser struct {
	called bool
}

func (s *mockSkillUser) UseSkill(casterID uint64, skillID uint32, targetID uint64, targetPos mathutil.Vec3) (bool, string) {
	s.called = true
	return true, ""
}

func newTestMob() *Mob {
	def := &MobDef{
		ID:          1,
		Name:        "TestMob",
		Level:       1,
		HP:          100,
		MP:          0,
		Str:         10,
		Dex:         5,
		Int:         3,
		MoveSpeed:   5.0,
		AggroRange:  10.0,
		AttackRange: 2.5,
		AttackSkill: 1,
		LeashRange:  30.0,
	}
	return NewMob(1000, def, mathutil.NewVec3(50, 0, 50))
}

func TestIdleToChase(t *testing.T) {
	mob := newTestMob()
	player := &mockPlayer{id: 1, pos: mathutil.NewVec3(55, 0, 50), hp: 100, maxHP: 100, alive: true}

	provider := &mockProvider{
		entities: map[uint64]entity.Entity{1: player, 1000: mob},
	}
	skills := &mockSkillUser{}

	// Player is within aggro range (5 units < 10 aggro range).
	UpdateMob(mob, 50*time.Millisecond, provider, skills)

	if mob.State() != StateChase {
		t.Errorf("expected StateChase, got %d", mob.State())
	}
	if mob.TargetID() != 1 {
		t.Errorf("expected target 1, got %d", mob.TargetID())
	}
}

func TestChaseToAttack(t *testing.T) {
	mob := newTestMob()
	mob.SetState(StateChase)
	mob.SetTargetID(1)

	// Place player within attack range.
	player := &mockPlayer{id: 1, pos: mathutil.NewVec3(52, 0, 50), hp: 100, maxHP: 100, alive: true}

	provider := &mockProvider{
		entities: map[uint64]entity.Entity{1: player, 1000: mob},
	}
	skills := &mockSkillUser{}

	UpdateMob(mob, 50*time.Millisecond, provider, skills)

	if mob.State() != StateAttack {
		t.Errorf("expected StateAttack, got %d", mob.State())
	}
}

func TestAttackUsesSkill(t *testing.T) {
	mob := newTestMob()
	mob.SetState(StateAttack)
	mob.SetTargetID(1)

	player := &mockPlayer{id: 1, pos: mathutil.NewVec3(52, 0, 50), hp: 100, maxHP: 100, alive: true}

	provider := &mockProvider{
		entities: map[uint64]entity.Entity{1: player, 1000: mob},
	}
	skills := &mockSkillUser{}

	UpdateMob(mob, 50*time.Millisecond, provider, skills)

	if !skills.called {
		t.Error("expected skill to be used")
	}
}

func TestLeashReturn(t *testing.T) {
	mob := newTestMob()
	mob.SetState(StateChase)
	mob.SetTargetID(1)

	// Move mob far from spawn.
	mob.SetPosition(mathutil.NewVec3(100, 0, 100)) // >30 units from spawn(50,0,50)

	player := &mockPlayer{id: 1, pos: mathutil.NewVec3(110, 0, 110), hp: 100, maxHP: 100, alive: true}

	provider := &mockProvider{
		entities: map[uint64]entity.Entity{1: player, 1000: mob},
	}
	skills := &mockSkillUser{}

	UpdateMob(mob, 50*time.Millisecond, provider, skills)

	if mob.State() != StateReturn {
		t.Errorf("expected StateReturn (leash), got %d", mob.State())
	}
}

func TestReturnToIdle(t *testing.T) {
	mob := newTestMob()
	mob.SetState(StateReturn)
	mob.SetPosition(mathutil.NewVec3(50.1, 0, 50.1)) // very close to spawn

	skills := &mockSkillUser{}
	provider := &mockProvider{entities: map[uint64]entity.Entity{1000: mob}}

	UpdateMob(mob, 50*time.Millisecond, provider, skills)

	if mob.State() != StateIdle {
		t.Errorf("expected StateIdle after return, got %d", mob.State())
	}
	if mob.HP() != mob.MaxHP() {
		t.Error("mob should heal to full on return")
	}
}

func TestTargetDiesReturnToSpawn(t *testing.T) {
	mob := newTestMob()
	mob.SetState(StateChase)
	mob.SetTargetID(1)

	// Target is dead.
	player := &mockPlayer{id: 1, pos: mathutil.NewVec3(55, 0, 50), hp: 0, maxHP: 100, alive: false}

	provider := &mockProvider{
		entities: map[uint64]entity.Entity{1: player, 1000: mob},
	}
	skills := &mockSkillUser{}

	UpdateMob(mob, 50*time.Millisecond, provider, skills)

	if mob.State() != StateReturn {
		t.Errorf("expected StateReturn when target dies, got %d", mob.State())
	}
}

func TestMobReset(t *testing.T) {
	mob := newTestMob()
	mob.SetHP(0)
	mob.SetPosition(mathutil.NewVec3(100, 0, 100))
	mob.SetState(StateDead)

	mob.Reset()

	if mob.HP() != mob.MaxHP() {
		t.Error("HP should be full after reset")
	}
	if mob.Position() != mob.SpawnPos() {
		t.Error("position should be spawn pos after reset")
	}
	if mob.State() != StateIdle {
		t.Error("state should be Idle after reset")
	}
}
