package channelserver

import (
	"fmt"

	"github.com/jmoiron/sqlx"
)

// HouseRepository centralizes all database access for house-related tables
// (user_binary house columns, warehouse, titles).
type HouseRepository struct {
	db *sqlx.DB
}

// NewHouseRepository creates a new HouseRepository.
func NewHouseRepository(db *sqlx.DB) *HouseRepository {
	return &HouseRepository{db: db}
}

// user_binary house columns

// UpdateInterior saves the house furniture layout.
func (r *HouseRepository) UpdateInterior(charID uint32, data []byte) error {
	_, err := r.db.Exec(`UPDATE user_binary SET house_furniture=$1 WHERE id=$2`, data, charID)
	return err
}

const houseQuery = `SELECT c.id, hr, gr, name, COALESCE(ub.house_state, 2) as house_state, COALESCE(ub.house_password, '') as house_password
	FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE c.id=$1`

// GetHouseByCharID returns house data for a single character.
func (r *HouseRepository) GetHouseByCharID(charID uint32) (HouseData, error) {
	var house HouseData
	err := r.db.QueryRowx(houseQuery, charID).StructScan(&house)
	return house, err
}

// SearchHousesByName returns houses matching a name pattern (case-insensitive).
func (r *HouseRepository) SearchHousesByName(name string) ([]HouseData, error) {
	var houses []HouseData
	rows, err := r.db.Queryx(
		`SELECT c.id, hr, gr, name, COALESCE(ub.house_state, 2) as house_state, COALESCE(ub.house_password, '') as house_password
		FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE name ILIKE $1`,
		fmt.Sprintf(`%%%s%%`, name),
	)
	if err != nil {
		return nil, err
	}
	defer func() { _ = rows.Close() }()
	for rows.Next() {
		var house HouseData
		if err := rows.StructScan(&house); err == nil {
			houses = append(houses, house)
		}
	}
	return houses, nil
}

// UpdateHouseState sets the house visibility state and password.
func (r *HouseRepository) UpdateHouseState(charID uint32, state uint8, password string) error {
	_, err := r.db.Exec(`UPDATE user_binary SET house_state=$1, house_password=$2 WHERE id=$3`, state, password, charID)
	return err
}

// GetHouseAccess returns the house state and password for access control checks.
func (r *HouseRepository) GetHouseAccess(charID uint32) (state uint8, password string, err error) {
	state = 2 // default to password-protected
	err = r.db.QueryRow(
		`SELECT COALESCE(house_state, 2) as house_state, COALESCE(house_password, '') as house_password FROM user_binary WHERE id=$1`,
		charID,
	).Scan(&state, &password)
	return
}

// GetHouseContents returns all house content columns for rendering a house visit.
func (r *HouseRepository) GetHouseContents(charID uint32) (houseTier, houseData, houseFurniture, bookshelf, gallery, tore, garden []byte, err error) {
	err = r.db.QueryRow(
		`SELECT house_tier, house_data, house_furniture, bookshelf, gallery, tore, garden FROM user_binary WHERE id=$1`,
		charID,
	).Scan(&houseTier, &houseData, &houseFurniture, &bookshelf, &gallery, &tore, &garden)
	return
}

// GetMission returns the myhouse mission data.
func (r *HouseRepository) GetMission(charID uint32) ([]byte, error) {
	var data []byte
	err := r.db.QueryRow(`SELECT mission FROM user_binary WHERE id=$1`, charID).Scan(&data)
	return data, err
}

// UpdateMission saves the myhouse mission data.
func (r *HouseRepository) UpdateMission(charID uint32, data []byte) error {
	_, err := r.db.Exec(`UPDATE user_binary SET mission=$1 WHERE id=$2`, data, charID)
	return err
}

// Warehouse methods

// InitializeWarehouse ensures a warehouse row exists for the character.
func (r *HouseRepository) InitializeWarehouse(charID uint32) error {
	var t int
	err := r.db.QueryRow(`SELECT character_id FROM warehouse WHERE character_id=$1`, charID).Scan(&t)
	if err != nil {
		_, err = r.db.Exec(`INSERT INTO warehouse (character_id) VALUES ($1)`, charID)
		return err
	}
	return nil
}

const warehouseNamesSQL = `
SELECT
COALESCE(item0name, ''),
COALESCE(item1name, ''),
COALESCE(item2name, ''),
COALESCE(item3name, ''),
COALESCE(item4name, ''),
COALESCE(item5name, ''),
COALESCE(item6name, ''),
COALESCE(item7name, ''),
COALESCE(item8name, ''),
COALESCE(item9name, ''),
COALESCE(equip0name, ''),
COALESCE(equip1name, ''),
COALESCE(equip2name, ''),
COALESCE(equip3name, ''),
COALESCE(equip4name, ''),
COALESCE(equip5name, ''),
COALESCE(equip6name, ''),
COALESCE(equip7name, ''),
COALESCE(equip8name, ''),
COALESCE(equip9name, '')
FROM warehouse WHERE character_id=$1`

// GetWarehouseNames returns item and equipment box names.
func (r *HouseRepository) GetWarehouseNames(charID uint32) (itemNames, equipNames [10]string, err error) {
	err = r.db.QueryRow(warehouseNamesSQL, charID).Scan(
		&itemNames[0], &itemNames[1], &itemNames[2], &itemNames[3], &itemNames[4],
		&itemNames[5], &itemNames[6], &itemNames[7], &itemNames[8], &itemNames[9],
		&equipNames[0], &equipNames[1], &equipNames[2], &equipNames[3], &equipNames[4],
		&equipNames[5], &equipNames[6], &equipNames[7], &equipNames[8], &equipNames[9],
	)
	return
}

// RenameWarehouseBox renames an item or equipment warehouse box.
// boxType 0 = items, 1 = equipment. boxIndex must be 0-9.
func (r *HouseRepository) RenameWarehouseBox(charID uint32, boxType uint8, boxIndex uint8, name string) error {
	var col string
	switch boxType {
	case 0:
		col = fmt.Sprintf("item%dname", boxIndex)
	case 1:
		col = fmt.Sprintf("equip%dname", boxIndex)
	default:
		return fmt.Errorf("invalid box type: %d", boxType)
	}
	_, err := r.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s=$1 WHERE character_id=$2", col), name, charID)
	return err
}

// GetWarehouseItemData returns raw serialized item data for a warehouse box.
// index 0-10 (10 = gift box).
func (r *HouseRepository) GetWarehouseItemData(charID uint32, index uint8) ([]byte, error) {
	var data []byte
	err := r.db.QueryRow(fmt.Sprintf(`SELECT item%d FROM warehouse WHERE character_id=$1`, index), charID).Scan(&data)
	return data, err
}

// SetWarehouseItemData saves raw serialized item data for a warehouse box.
func (r *HouseRepository) SetWarehouseItemData(charID uint32, index uint8, data []byte) error {
	_, err := r.db.Exec(fmt.Sprintf(`UPDATE warehouse SET item%d=$1 WHERE character_id=$2`, index), data, charID)
	return err
}

// GetWarehouseEquipData returns raw serialized equipment data for a warehouse box.
func (r *HouseRepository) GetWarehouseEquipData(charID uint32, index uint8) ([]byte, error) {
	var data []byte
	err := r.db.QueryRow(fmt.Sprintf(`SELECT equip%d FROM warehouse WHERE character_id=$1`, index), charID).Scan(&data)
	return data, err
}

// SetWarehouseEquipData saves raw serialized equipment data for a warehouse box.
func (r *HouseRepository) SetWarehouseEquipData(charID uint32, index uint8, data []byte) error {
	_, err := r.db.Exec(fmt.Sprintf(`UPDATE warehouse SET equip%d=$1 WHERE character_id=$2`, index), data, charID)
	return err
}

// Title methods

// GetTitles returns all titles for a character.
func (r *HouseRepository) GetTitles(charID uint32) ([]Title, error) {
	var titles []Title
	rows, err := r.db.Queryx(`SELECT id, unlocked_at, updated_at FROM titles WHERE char_id=$1`, charID)
	if err != nil {
		return nil, err
	}
	defer func() { _ = rows.Close() }()
	for rows.Next() {
		var title Title
		if err := rows.StructScan(&title); err == nil {
			titles = append(titles, title)
		}
	}
	return titles, nil
}

// AcquireTitle inserts a new title or updates its timestamp if it already exists.
func (r *HouseRepository) AcquireTitle(titleID uint16, charID uint32) error {
	var exists int
	err := r.db.QueryRow(`SELECT count(*) FROM titles WHERE id=$1 AND char_id=$2`, titleID, charID).Scan(&exists)
	if err != nil || exists == 0 {
		_, err = r.db.Exec(`INSERT INTO titles VALUES ($1, $2, now(), now())`, titleID, charID)
	} else {
		_, err = r.db.Exec(`UPDATE titles SET updated_at=now() WHERE id=$1 AND char_id=$2`, titleID, charID)
	}
	return err
}
