package channelserver

import (
	"time"
)

// Repository interfaces decouple handlers from concrete PostgreSQL implementations,
// enabling mock/stub injection for unit tests and alternative storage backends.

// CharacterRepo defines the contract for character data access.
type CharacterRepo interface {
	LoadColumn(charID uint32, column string) ([]byte, error)
	SaveColumn(charID uint32, column string, data []byte) error
	ReadInt(charID uint32, column string) (int, error)
	AdjustInt(charID uint32, column string, delta int) (int, error)
	GetName(charID uint32) (string, error)
	GetUserID(charID uint32) (uint32, error)
	UpdateLastLogin(charID uint32, timestamp int64) error
	UpdateTimePlayed(charID uint32, timePlayed int) error
	GetCharIDsByUserID(userID uint32) ([]uint32, error)
	ReadTime(charID uint32, column string, defaultVal time.Time) (time.Time, error)
	SaveTime(charID uint32, column string, value time.Time) error
	SaveInt(charID uint32, column string, value int) error
	SaveBool(charID uint32, column string, value bool) error
	SaveString(charID uint32, column string, value string) error
	ReadBool(charID uint32, column string) (bool, error)
	ReadString(charID uint32, column string) (string, error)
	LoadColumnWithDefault(charID uint32, column string, defaultVal []byte) ([]byte, error)
	SetDeleted(charID uint32) error
	UpdateDailyCafe(charID uint32, dailyTime time.Time, bonusQuests, dailyQuests uint32) error
	ResetDailyQuests(charID uint32) error
	ReadEtcPoints(charID uint32) (bonusQuests, dailyQuests, promoPoints uint32, err error)
	ResetCafeTime(charID uint32, cafeReset time.Time) error
	UpdateGuildPostChecked(charID uint32) error
	ReadGuildPostChecked(charID uint32) (time.Time, error)
	SaveMercenary(charID uint32, data []byte, rastaID uint32) error
	UpdateGCPAndPact(charID uint32, gcp uint32, pactID uint32) error
	FindByRastaID(rastaID int) (charID uint32, name string, err error)
	SaveCharacterData(charID uint32, compSave []byte, hr, gr uint16, isFemale bool, weaponType uint8, weaponID uint16) error
	SaveHouseData(charID uint32, houseTier []byte, houseData, bookshelf, gallery, tore, garden []byte) error
	LoadSaveData(charID uint32) (uint32, []byte, bool, string, error)
	SaveBackup(charID uint32, slot int, data []byte) error
	GetLastBackupTime(charID uint32) (time.Time, error)
	// SaveCharacterDataAtomic performs all save-related writes in a single
	// database transaction: character data, house data, checksum, and
	// optionally a backup snapshot. If any step fails, everything is rolled back.
	SaveCharacterDataAtomic(params SaveAtomicParams) error
	// LoadSaveDataWithHash loads savedata along with its stored SHA-256 hash.
	// The hash may be nil for characters saved before checksums were introduced.
	LoadSaveDataWithHash(charID uint32) (id uint32, savedata []byte, isNew bool, name string, hash []byte, err error)
	// LoadBackupsByRecency returns all backup slots for a character ordered
	// most-recent first. Returns an empty slice if no backups exist.
	LoadBackupsByRecency(charID uint32) ([]SavedataBackup, error)
}

// GuildRepo defines the contract for guild data access.
type GuildRepo interface {
	GetByID(guildID uint32) (*Guild, error)
	GetByCharID(charID uint32) (*Guild, error)
	ListAll() ([]*Guild, error)
	Create(leaderCharID uint32, guildName string) (int32, error)
	Save(guild *Guild) error
	Disband(guildID uint32) error
	RemoveCharacter(charID uint32) error
	AcceptApplication(guildID, charID uint32) error
	CreateApplication(guildID, charID, actorID uint32, appType GuildApplicationType) error
	CreateInviteWithMail(guildID, charID, actorID uint32, mailSenderID, mailRecipientID uint32, mailSubject, mailBody string) error
	HasInvite(guildID, charID uint32) (bool, error)
	CancelInvite(inviteID uint32) error
	AcceptInvite(guildID, charID uint32) error
	DeclineInvite(guildID, charID uint32) error
	RejectApplication(guildID, charID uint32) error
	ArrangeCharacters(charIDs []uint32) error
	GetApplication(guildID, charID uint32, appType GuildApplicationType) (*GuildApplication, error)
	HasApplication(guildID, charID uint32) (bool, error)
	GetItemBox(guildID uint32) ([]byte, error)
	SaveItemBox(guildID uint32, data []byte) error
	GetMembers(guildID uint32, applicants bool) ([]*GuildMember, error)
	GetCharacterMembership(charID uint32) (*GuildMember, error)
	SaveMember(member *GuildMember) error
	SetRecruiting(guildID uint32, recruiting bool) error
	SetPugiOutfits(guildID uint32, outfits uint32) error
	SetRecruiter(charID uint32, allowed bool) error
	AddMemberDailyRP(charID uint32, amount uint16) error
	ExchangeEventRP(guildID uint32, amount uint16) (uint32, error)
	AddRankRP(guildID uint32, amount uint16) error
	AddEventRP(guildID uint32, amount uint16) error
	GetRoomRP(guildID uint32) (uint16, error)
	SetRoomRP(guildID uint32, rp uint16) error
	AddRoomRP(guildID uint32, amount uint16) error
	SetRoomExpiry(guildID uint32, expiry time.Time) error
	ListPosts(guildID uint32, postType int) ([]*MessageBoardPost, error)
	CreatePost(guildID, authorID, stampID uint32, postType int, title, body string, maxPosts int) error
	DeletePost(postID uint32) error
	UpdatePost(postID uint32, title, body string) error
	UpdatePostStamp(postID, stampID uint32) error
	GetPostLikedBy(postID uint32) (string, error)
	SetPostLikedBy(postID uint32, likedBy string) error
	CountNewPosts(guildID uint32, since time.Time) (int, error)
	GetAllianceByID(allianceID uint32) (*GuildAlliance, error)
	ListAlliances() ([]*GuildAlliance, error)
	CreateAlliance(name string, parentGuildID uint32) error
	DeleteAlliance(allianceID uint32) error
	RemoveGuildFromAlliance(allianceID, guildID, subGuild1ID, subGuild2ID uint32) error
	SetAllianceRecruiting(allianceID uint32, recruiting bool) error
	ListAdventures(guildID uint32) ([]*GuildAdventure, error)
	CreateAdventure(guildID, destination uint32, depart, returnTime int64) error
	CreateAdventureWithCharge(guildID, destination, charge uint32, depart, returnTime int64) error
	CollectAdventure(adventureID uint32, charID uint32) error
	ChargeAdventure(adventureID uint32, amount uint32) error
	GetPendingHunt(charID uint32) (*TreasureHunt, error)
	ListGuildHunts(guildID, charID uint32) ([]*TreasureHunt, error)
	CreateHunt(guildID, hostID, destination, level uint32, huntData []byte, catsUsed string) error
	AcquireHunt(huntID uint32) error
	RegisterHuntReport(huntID, charID uint32) error
	CollectHunt(huntID uint32) error
	ClaimHuntReward(huntID, charID uint32) error
	ListMeals(guildID uint32) ([]*GuildMeal, error)
	CreateMeal(guildID, mealID, level uint32, createdAt time.Time) (uint32, error)
	UpdateMeal(mealID, newMealID, level uint32, createdAt time.Time) error
	ClaimHuntBox(charID uint32, claimedAt time.Time) error
	ListGuildKills(guildID, charID uint32) ([]*GuildKill, error)
	CountGuildKills(guildID, charID uint32) (int, error)
	ClearTreasureHunt(charID uint32) error
	InsertKillLog(charID uint32, monster int, quantity uint8, timestamp time.Time) error
	ListInvites(guildID uint32) ([]*GuildInvite, error)
	RolloverDailyRP(guildID uint32, noon time.Time) error
	AddWeeklyBonusUsers(guildID uint32, numUsers uint8) error
	FindOrCreateReturnGuild(returnType uint8, nameTemplate string) (uint32, error)
	AddMember(guildID, charID uint32) error
}

// UserRepo defines the contract for user account data access.
type UserRepo interface {
	GetGachaPoints(userID uint32) (fp, premium, trial uint32, err error)
	GetTrialCoins(userID uint32) (uint16, error)
	DeductTrialCoins(userID uint32, amount uint32) error
	DeductPremiumCoins(userID uint32, amount uint32) error
	AddPremiumCoins(userID uint32, amount uint32) error
	AddTrialCoins(userID uint32, amount uint32) error
	DeductFrontierPoints(userID uint32, amount uint32) error
	AddFrontierPoints(userID uint32, amount uint32) error
	AdjustFrontierPointsDeduct(userID uint32, amount int) (uint32, error)
	AdjustFrontierPointsCredit(userID uint32, amount int) (uint32, error)
	AddFrontierPointsFromGacha(userID uint32, gachaID uint32, entryType uint8) error
	GetRights(userID uint32) (uint32, error)
	SetRights(userID uint32, rights uint32) error
	IsOp(userID uint32) (bool, error)
	SetLastCharacter(userID uint32, charID uint32) error
	GetTimer(userID uint32) (bool, error)
	SetTimer(userID uint32, value bool) error
	CountByPSNID(psnID string) (int, error)
	SetPSNID(userID uint32, psnID string) error
	GetDiscordToken(userID uint32) (string, error)
	SetDiscordToken(userID uint32, token string) error
	GetItemBox(userID uint32) ([]byte, error)
	SetItemBox(userID uint32, data []byte) error
	LinkDiscord(discordID string, token string) (string, error)
	SetPasswordByDiscordID(discordID string, hash []byte) error
	GetByIDAndUsername(charID uint32) (userID uint32, username string, err error)
	BanUser(userID uint32, expires *time.Time) error
	// GetLanguage returns the user's preferred language code (e.g. "en", "jp").
	// An empty string means the user has no preference set and the server
	// default should be used.
	GetLanguage(userID uint32) (string, error)
	// SetLanguage stores the user's preferred language code. Passing an empty
	// string clears the preference (reverts to server default on next login).
	SetLanguage(userID uint32, lang string) error
}

// GachaRepo defines the contract for gacha system data access.
type GachaRepo interface {
	GetEntryForTransaction(gachaID uint32, rollID uint8) (itemType uint8, itemNumber uint16, rolls int, err error)
	GetRewardPool(gachaID uint32) ([]GachaEntry, error)
	GetItemsForEntry(entryID uint32) ([]GachaItem, error)
	GetGuaranteedItems(rollType uint8, gachaID uint32) ([]GachaItem, error)
	GetStepupStep(gachaID uint32, charID uint32) (uint8, error)
	GetStepupWithTime(gachaID uint32, charID uint32) (uint8, time.Time, error)
	HasEntryType(gachaID uint32, entryType uint8) (bool, error)
	DeleteStepup(gachaID uint32, charID uint32) error
	InsertStepup(gachaID uint32, step uint8, charID uint32) error
	GetBoxEntryIDs(gachaID uint32, charID uint32) ([]uint32, error)
	InsertBoxEntry(gachaID uint32, entryID uint32, charID uint32) error
	DeleteBoxEntries(gachaID uint32, charID uint32) error
	ListShop() ([]Gacha, error)
	GetShopType(shopID uint32) (int, error)
	GetAllEntries(gachaID uint32) ([]GachaEntry, error)
	GetWeightDivisor(gachaID uint32) (float64, error)
}

// HouseRepo defines the contract for house/housing data access.
type HouseRepo interface {
	UpdateInterior(charID uint32, data []byte) error
	GetHouseByCharID(charID uint32) (HouseData, error)
	SearchHousesByName(name string) ([]HouseData, error)
	UpdateHouseState(charID uint32, state uint8, password string) error
	GetHouseAccess(charID uint32) (state uint8, password string, err error)
	GetHouseContents(charID uint32) (houseTier, houseData, houseFurniture, bookshelf, gallery, tore, garden []byte, err error)
	GetMission(charID uint32) ([]byte, error)
	UpdateMission(charID uint32, data []byte) error
	InitializeWarehouse(charID uint32) error
	GetWarehouseNames(charID uint32) (itemNames, equipNames [10]string, err error)
	RenameWarehouseBox(charID uint32, boxType uint8, boxIndex uint8, name string) error
	GetWarehouseItemData(charID uint32, index uint8) ([]byte, error)
	SetWarehouseItemData(charID uint32, index uint8, data []byte) error
	GetWarehouseEquipData(charID uint32, index uint8) ([]byte, error)
	SetWarehouseEquipData(charID uint32, index uint8, data []byte) error
	GetTitles(charID uint32) ([]Title, error)
	AcquireTitle(titleID uint16, charID uint32) error
}

// FestaRepo defines the contract for festa event data access.
type FestaRepo interface {
	CleanupAll() error
	InsertEvent(startTime uint32) error
	GetFestaEvents() ([]FestaEvent, error)
	GetTeamSouls(team string) (uint32, error)
	GetTrialsWithMonopoly() ([]FestaTrial, error)
	GetTopGuildForTrial(trialType uint16) (FestaGuildRanking, error)
	GetTopGuildInWindow(start, end uint32) (FestaGuildRanking, error)
	GetCharSouls(charID uint32) (uint32, error)
	HasClaimedMainPrize(charID uint32) bool
	VoteTrial(charID uint32, trialID uint32) error
	RegisterGuild(guildID uint32, team string) error
	SubmitSouls(charID, guildID uint32, souls []uint16) error
	ClaimPrize(prizeID uint32, charID uint32) error
	ListPrizes(charID uint32, prizeType string) ([]Prize, error)
}

// TowerRepo defines the contract for tower/tenrouirai data access.
type TowerRepo interface {
	GetTowerData(charID uint32) (TowerData, error)
	GetSkills(charID uint32) (string, error)
	UpdateSkills(charID uint32, skills string, cost int32) error
	UpdateProgress(charID uint32, tr, trp, cost, block1 int32) error
	GetGems(charID uint32) (string, error)
	UpdateGems(charID uint32, gems string) error
	GetTenrouiraiProgress(guildID uint32) (TenrouiraiProgressData, error)
	GetTenrouiraiMissionScores(guildID uint32, missionIndex uint8) ([]TenrouiraiCharScore, error)
	GetGuildTowerRP(guildID uint32) (uint32, error)
	GetGuildTowerPageAndRP(guildID uint32) (page int, donated int, err error)
	AdvanceTenrouiraiPage(guildID uint32) error
	DonateGuildTowerRP(guildID uint32, rp uint16) error
}

// RengokuRepo defines the contract for rengoku score/ranking data access.
type RengokuRepo interface {
	UpsertScore(charID uint32, maxStagesMp, maxPointsMp, maxStagesSp, maxPointsSp uint32) error
	GetRanking(leaderboard uint32, guildID uint32) ([]RengokuScore, error)
}

// MailRepo defines the contract for in-game mail data access.
type MailRepo interface {
	SendMail(senderID, recipientID uint32, subject, body string, itemID, itemAmount uint16, isGuildInvite, isSystemMessage bool) error
	GetListForCharacter(charID uint32) ([]Mail, error)
	GetByID(id int) (*Mail, error)
	MarkRead(id int) error
	MarkDeleted(id int) error
	SetLocked(id int, locked bool) error
	MarkItemReceived(id int) error
}

// StampRepo defines the contract for stamp card data access.
type StampRepo interface {
	GetChecked(charID uint32, stampType string) (time.Time, error)
	Init(charID uint32, now time.Time) error
	SetChecked(charID uint32, stampType string, now time.Time) error
	IncrementTotal(charID uint32, stampType string) error
	GetTotals(charID uint32, stampType string) (total, redeemed uint16, err error)
	ExchangeYearly(charID uint32) (total, redeemed uint16, err error)
	Exchange(charID uint32, stampType string) (total, redeemed uint16, err error)
	GetMonthlyClaimed(charID uint32, monthlyType string) (time.Time, error)
	SetMonthlyClaimed(charID uint32, monthlyType string, now time.Time) error
}

// DistributionRepo defines the contract for distribution/event item data access.
type DistributionRepo interface {
	List(charID uint32, distType uint8) ([]Distribution, error)
	GetItems(distributionID uint32) ([]DistributionItem, error)
	RecordAccepted(distributionID, charID uint32) error
	GetDescription(distributionID uint32) (string, error)
}

// SessionRepo defines the contract for session/login token data access.
type SessionRepo interface {
	ValidateLoginToken(token string, sessionID uint32, charID uint32) error
	BindSession(token string, serverID uint16, charID uint32) error
	ClearSession(token string) error
	UpdatePlayerCount(serverID uint16, count int) error
}

// EventRepo defines the contract for event/login boost data access.
type EventRepo interface {
	GetFeatureWeapon(startTime time.Time) (activeFeature, error)
	InsertFeatureWeapon(startTime time.Time, features uint32) error
	GetLoginBoosts(charID uint32) ([]loginBoost, error)
	InsertLoginBoost(charID uint32, weekReq uint8, expiration, reset time.Time) error
	UpdateLoginBoost(charID uint32, weekReq uint8, expiration, reset time.Time) error
	GetEventQuests() ([]EventQuest, error)
	UpdateEventQuestStartTimes(updates []EventQuestUpdate) error
}

// AchievementRepo defines the contract for achievement data access.
type AchievementRepo interface {
	EnsureExists(charID uint32) error
	GetAllScores(charID uint32) ([33]int32, error)
	IncrementScore(charID uint32, achievementID uint8) error
	GetDisplayedLevels(charID uint32) ([]byte, error)
	SaveDisplayedLevels(charID uint32, levels []byte) error
}

// ShopRepo defines the contract for shop data access.
type ShopRepo interface {
	GetShopItems(shopType uint8, shopID uint32, charID uint32) ([]ShopItem, error)
	RecordPurchase(charID, shopItemID, quantity uint32) error
	GetFpointItem(tradeID uint32) (quantity, fpoints int, err error)
	GetFpointExchangeList() ([]FPointExchange, error)
}

// CafeRepo defines the contract for cafe bonus data access.
type CafeRepo interface {
	ResetAccepted(charID uint32) error
	GetBonuses(charID uint32) ([]CafeBonus, error)
	GetClaimable(charID uint32, elapsedSec int64) ([]CafeBonus, error)
	GetBonusItem(bonusID uint32) (itemType, quantity uint32, err error)
	AcceptBonus(bonusID, charID uint32) error
}

// GoocooRepo defines the contract for goocoo (pet) data access.
type GoocooRepo interface {
	EnsureExists(charID uint32) error
	GetSlot(charID uint32, slot uint32) ([]byte, error)
	ClearSlot(charID uint32, slot uint32) error
	SaveSlot(charID uint32, slot uint32, data []byte) error
}

// DivaPrize represents a single reward milestone for the personal or guild track.
type DivaPrize struct {
	ID         int
	Type       string
	PointsReq  int
	ItemType   int
	ItemID     int
	Quantity   int
	GR         bool
	Repeatable bool
}

// DivaRepo defines the contract for diva event data access.
type DivaRepo interface {
	DeleteEvents() error
	InsertEvent(startEpoch uint32) error
	GetEvents() ([]DivaEvent, error)
	AddPoints(charID uint32, eventID uint32, questPoints, bonusPoints uint32) error
	GetPoints(charID uint32, eventID uint32) (questPoints, bonusPoints int64, err error)
	GetTotalPoints(eventID uint32) (questPoints, bonusPoints int64, err error)

	// Bead management
	GetBeads() ([]int, error)
	AssignBead(characterID uint32, beadIndex int, expiry time.Time) error
	AddBeadPoints(characterID uint32, beadIndex int, points int) error
	GetCharacterBeadPoints(characterID uint32) (map[int]int, error)
	GetTotalBeadPoints() (int64, error)
	GetTopBeadPerDay(day int) (int, error)
	CleanupBeads() error

	// Prize rewards
	GetPersonalPrizes() ([]DivaPrize, error)
	GetGuildPrizes() ([]DivaPrize, error)

	// Interception points (guild_characters.interception_points JSON)
	GetCharacterInterceptionPoints(characterID uint32) (map[string]int, error)
	AddInterceptionPoints(characterID uint32, questFileID int, points int) error
}

// MiscRepo defines the contract for miscellaneous data access.
type MiscRepo interface {
	GetTrendWeapons(weaponType uint8) ([]uint16, error)
	UpsertTrendWeapon(weaponID uint16, weaponType uint8) error
}

// ScenarioRepo defines the contract for scenario counter data access.
type ScenarioRepo interface {
	GetCounters() ([]Scenario, error)
}

// MercenaryRepo defines the contract for mercenary/rasta data access.
type MercenaryRepo interface {
	NextRastaID() (uint32, error)
	NextAirouID() (uint32, error)
	GetMercenaryLoans(charID uint32) ([]MercenaryLoan, error)
	GetGuildHuntCatsUsed(charID uint32) ([]GuildHuntCatUsage, error)
	GetGuildAirou(guildID uint32) ([][]byte, error)
}

// Tournament represents a tournament schedule entry.
type Tournament struct {
	ID         uint32 `db:"id"`
	Name       string `db:"name"`
	StartTime  int64  `db:"start_time"`
	EntryEnd   int64  `db:"entry_end"`
	RankingEnd int64  `db:"ranking_end"`
	RewardEnd  int64  `db:"reward_end"`
}

// TournamentCup represents a competition category within a tournament.
type TournamentCup struct {
	ID          uint32 `db:"id"`
	CupGroup    int16  `db:"cup_group"`
	CupType     int16  `db:"cup_type"`
	Unk         int16  `db:"unk"`
	Name        string `db:"name"`
	Description string `db:"description"`
}

// TournamentSubEvent represents a specific hunt/fish target within a cup group.
type TournamentSubEvent struct {
	ID           uint32 `db:"id"`
	CupGroup     int16  `db:"cup_group"`
	EventSubType int16  `db:"event_sub_type"`
	QuestFileID  uint32 `db:"quest_file_id"`
	Name         string `db:"name"`
}

// TournamentRankEntry is a single entry in a leaderboard.
type TournamentRankEntry struct {
	CharID    uint32
	Rank      uint32
	Grade     uint16
	HR        uint16
	GR        uint16
	CharName  string
	GuildName string
}

// TournamentEntry represents a player's registration for a tournament.
type TournamentEntry struct {
	ID           uint32 `db:"id"`
	CharID       uint32 `db:"char_id"`
	TournamentID uint32 `db:"tournament_id"`
}

// TournamentRepo defines the contract for tournament schedule and result data access.
type TournamentRepo interface {
	GetActive(now int64) (*Tournament, error)
	GetCups(tournamentID uint32) ([]TournamentCup, error)
	GetSubEvents() ([]TournamentSubEvent, error)
	Register(charID, tournamentID uint32) (entryID uint32, err error)
	GetEntry(charID, tournamentID uint32) (*TournamentEntry, error)
	SubmitResult(charID, tournamentID, eventID, questSlot, stageHandle uint32) error
	GetLeaderboard(eventID uint32) ([]TournamentRankEntry, error)
}
