package repository

import (
	"context"
	"fmt"

	"a301_game_server/internal/db"
)

// CharacterData holds persisted character state.
type CharacterData struct {
	ID        int64
	AccountID int64
	Name      string
	Level     int32
	Exp       int64
	HP        int32
	MaxHP     int32
	MP        int32
	MaxMP     int32
	Str       int32
	Dex       int32
	IntStat   int32
	ZoneID    int32
	PosX      float32
	PosY      float32
	PosZ      float32
	Rotation  float32
}

// CharacterRepo handles character persistence.
type CharacterRepo struct {
	pool *db.Pool
}

// NewCharacterRepo creates a new character repository.
func NewCharacterRepo(pool *db.Pool) *CharacterRepo {
	return &CharacterRepo{pool: pool}
}

// Create inserts a new character.
func (r *CharacterRepo) Create(ctx context.Context, accountID int64, name string) (*CharacterData, error) {
	c := &CharacterData{
		AccountID: accountID,
		Name:      name,
		Level:     1,
		HP:        100,
		MaxHP:     100,
		MP:        50,
		MaxMP:     50,
		Str:       10,
		Dex:       10,
		IntStat:   10,
		ZoneID:    1,
	}

	err := r.pool.QueryRow(ctx,
		`INSERT INTO characters (account_id, name, level, hp, max_hp, mp, max_mp, str, dex, int_stat, zone_id)
		 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
		 RETURNING id`,
		c.AccountID, c.Name, c.Level, c.HP, c.MaxHP, c.MP, c.MaxMP, c.Str, c.Dex, c.IntStat, c.ZoneID,
	).Scan(&c.ID)

	if err != nil {
		return nil, fmt.Errorf("create character: %w", err)
	}
	return c, nil
}

// GetByAccountID returns all characters for an account.
func (r *CharacterRepo) GetByAccountID(ctx context.Context, accountID int64) ([]*CharacterData, error) {
	rows, err := r.pool.Query(ctx,
		`SELECT id, account_id, name, level, exp, hp, max_hp, mp, max_mp, str, dex, int_stat,
		        zone_id, pos_x, pos_y, pos_z, rotation
		 FROM characters WHERE account_id = $1`, accountID,
	)
	if err != nil {
		return nil, fmt.Errorf("query characters: %w", err)
	}
	defer rows.Close()

	var chars []*CharacterData
	for rows.Next() {
		c := &CharacterData{}
		if err := rows.Scan(
			&c.ID, &c.AccountID, &c.Name, &c.Level, &c.Exp,
			&c.HP, &c.MaxHP, &c.MP, &c.MaxMP,
			&c.Str, &c.Dex, &c.IntStat,
			&c.ZoneID, &c.PosX, &c.PosY, &c.PosZ, &c.Rotation,
		); err != nil {
			return nil, fmt.Errorf("scan character: %w", err)
		}
		chars = append(chars, c)
	}
	return chars, nil
}

// GetByID loads a single character.
func (r *CharacterRepo) GetByID(ctx context.Context, id int64) (*CharacterData, error) {
	c := &CharacterData{}
	err := r.pool.QueryRow(ctx,
		`SELECT id, account_id, name, level, exp, hp, max_hp, mp, max_mp, str, dex, int_stat,
		        zone_id, pos_x, pos_y, pos_z, rotation
		 FROM characters WHERE id = $1`, id,
	).Scan(
		&c.ID, &c.AccountID, &c.Name, &c.Level, &c.Exp,
		&c.HP, &c.MaxHP, &c.MP, &c.MaxMP,
		&c.Str, &c.Dex, &c.IntStat,
		&c.ZoneID, &c.PosX, &c.PosY, &c.PosZ, &c.Rotation,
	)
	if err != nil {
		return nil, fmt.Errorf("get character %d: %w", id, err)
	}
	return c, nil
}

// Save persists the current character state.
func (r *CharacterRepo) Save(ctx context.Context, c *CharacterData) error {
	_, err := r.pool.Exec(ctx,
		`UPDATE characters SET
			level = $2, exp = $3, hp = $4, max_hp = $5, mp = $6, max_mp = $7,
			str = $8, dex = $9, int_stat = $10,
			zone_id = $11, pos_x = $12, pos_y = $13, pos_z = $14, rotation = $15,
			updated_at = NOW()
		 WHERE id = $1`,
		c.ID, c.Level, c.Exp, c.HP, c.MaxHP, c.MP, c.MaxMP,
		c.Str, c.Dex, c.IntStat,
		c.ZoneID, c.PosX, c.PosY, c.PosZ, c.Rotation,
	)
	if err != nil {
		return fmt.Errorf("save character %d: %w", c.ID, err)
	}
	return nil
}

// SaveBatch saves multiple characters in a single transaction.
func (r *CharacterRepo) SaveBatch(ctx context.Context, chars []*CharacterData) error {
	if len(chars) == 0 {
		return nil
	}

	tx, err := r.pool.Begin(ctx)
	if err != nil {
		return fmt.Errorf("begin tx: %w", err)
	}
	defer tx.Rollback(ctx)

	for _, c := range chars {
		_, err := tx.Exec(ctx,
			`UPDATE characters SET
				level = $2, exp = $3, hp = $4, max_hp = $5, mp = $6, max_mp = $7,
				str = $8, dex = $9, int_stat = $10,
				zone_id = $11, pos_x = $12, pos_y = $13, pos_z = $14, rotation = $15,
				updated_at = NOW()
			 WHERE id = $1`,
			c.ID, c.Level, c.Exp, c.HP, c.MaxHP, c.MP, c.MaxMP,
			c.Str, c.Dex, c.IntStat,
			c.ZoneID, c.PosX, c.PosY, c.PosZ, c.Rotation,
		)
		if err != nil {
			return fmt.Errorf("save character %d in batch: %w", c.ID, err)
		}
	}

	return tx.Commit(ctx)
}
