package player

import (
	"sync"

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

// Player represents an online player in the game world.
type Player struct {
	mu sync.RWMutex

	id       uint64
	charID   int64  // database character ID
	acctID   int64  // database account ID
	name     string
	position mathutil.Vec3
	rotation float32
	velocity mathutil.Vec3

	stats Stats

	conn    *network.Connection
	zoneID  uint32
	session *Session
	dirty   bool // true if state changed since last DB save
}

// NewPlayer creates a new player.
func NewPlayer(id uint64, name string, conn *network.Connection) *Player {
	return &Player{
		id:   id,
		name: name,
		conn: conn,
		stats: Stats{
			HP: 100, MaxHP: 100,
			MP: 50, MaxMP: 50,
			Str: 10, Dex: 10, Int: 10,
			Level: 1,
		},
	}
}

// NewPlayerFromDB creates a player from persisted character data.
func NewPlayerFromDB(data *repository.CharacterData, conn *network.Connection) *Player {
	return &Player{
		id:     uint64(data.ID),
		charID: data.ID,
		acctID: data.AccountID,
		name:   data.Name,
		position: mathutil.NewVec3(data.PosX, data.PosY, data.PosZ),
		rotation: data.Rotation,
		stats: Stats{
			HP: data.HP, MaxHP: data.MaxHP,
			MP: data.MP, MaxMP: data.MaxMP,
			Str: data.Str, Dex: data.Dex, Int: data.IntStat,
			Level: data.Level, Exp: data.Exp,
		},
		conn:   conn,
		zoneID: uint32(data.ZoneID),
	}
}

// ToCharacterData converts current state to a persistable format.
func (p *Player) ToCharacterData() *repository.CharacterData {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return &repository.CharacterData{
		ID:        p.charID,
		AccountID: p.acctID,
		Name:      p.name,
		Level:     p.stats.Level,
		Exp:       p.stats.Exp,
		HP:        p.stats.HP,
		MaxHP:     p.stats.MaxHP,
		MP:        p.stats.MP,
		MaxMP:     p.stats.MaxMP,
		Str:       p.stats.Str,
		Dex:       p.stats.Dex,
		IntStat:   p.stats.Int,
		ZoneID:    int32(p.zoneID),
		PosX:      p.position.X,
		PosY:      p.position.Y,
		PosZ:      p.position.Z,
		Rotation:  p.rotation,
	}
}

func (p *Player) EntityID() uint64        { return p.id }
func (p *Player) EntityType() entity.Type { return entity.TypePlayer }

func (p *Player) Position() mathutil.Vec3 {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return p.position
}

func (p *Player) SetPosition(pos mathutil.Vec3) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.position = pos
	p.dirty = true
}

func (p *Player) Rotation() float32 {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return p.rotation
}

func (p *Player) SetRotation(rot float32) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.rotation = rot
	p.dirty = true
}

func (p *Player) Velocity() mathutil.Vec3 {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return p.velocity
}

func (p *Player) SetVelocity(vel mathutil.Vec3) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.velocity = vel
}

func (p *Player) Name() string                    { return p.name }
func (p *Player) CharID() int64                   { return p.charID }
func (p *Player) AccountID() int64                { return p.acctID }
func (p *Player) Connection() *network.Connection { return p.conn }
func (p *Player) ZoneID() uint32                  { return p.zoneID }
func (p *Player) SetZoneID(id uint32)             { p.zoneID = id }

func (p *Player) Stats() combat.CombatStats {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return combat.CombatStats{
		Str:   p.stats.Str,
		Dex:   p.stats.Dex,
		Int:   p.stats.Int,
		Level: p.stats.Level,
	}
}

func (p *Player) RawStats() Stats {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return p.stats
}

func (p *Player) SetStats(s Stats) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.stats = s
	p.dirty = true
}

func (p *Player) HP() int32 {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return p.stats.HP
}

func (p *Player) SetHP(hp int32) {
	p.mu.Lock()
	defer p.mu.Unlock()
	if hp < 0 {
		hp = 0
	}
	if hp > p.stats.MaxHP {
		hp = p.stats.MaxHP
	}
	p.stats.HP = hp
	p.dirty = true
}

func (p *Player) MaxHP() int32 {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return p.stats.MaxHP
}

func (p *Player) MP() int32 {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return p.stats.MP
}

func (p *Player) SetMP(mp int32) {
	p.mu.Lock()
	defer p.mu.Unlock()
	if mp < 0 {
		mp = 0
	}
	if mp > p.stats.MaxMP {
		mp = p.stats.MaxMP
	}
	p.stats.MP = mp
	p.dirty = true
}

func (p *Player) Level() int32 {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return p.stats.Level
}

func (p *Player) IsAlive() bool {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return p.stats.HP > 0
}

// IsDirty returns true if state has changed since last save.
func (p *Player) IsDirty() bool {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return p.dirty
}

// ClearDirty resets the dirty flag after a successful save.
func (p *Player) ClearDirty() {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.dirty = false
}

func (p *Player) ToProto() *pb.EntityState {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return &pb.EntityState{
		EntityId:   p.id,
		Name:       p.name,
		Position:   &pb.Vector3{X: p.position.X, Y: p.position.Y, Z: p.position.Z},
		Rotation:   p.rotation,
		Hp:         p.stats.HP,
		MaxHp:      p.stats.MaxHP,
		Level:      p.stats.Level,
		EntityType: pb.EntityType_ENTITY_TYPE_PLAYER,
	}
}
