package channelserver

import (
	"erupe-ce/common/byteframe"
	ps "erupe-ce/common/pascalstring"
	cfg "erupe-ce/config"
	"erupe-ce/network/mhfpacket"
	"time"

	"go.uber.org/zap"
)

// Distribution represents an item distribution event.
type Distribution struct {
	ID              uint32    `db:"id"`
	Deadline        time.Time `db:"deadline"`
	Rights          uint32    `db:"rights"`
	TimesAcceptable uint16    `db:"times_acceptable"`
	TimesAccepted   uint16    `db:"times_accepted"`
	MinHR           int16     `db:"min_hr"`
	MaxHR           int16     `db:"max_hr"`
	MinSR           int16     `db:"min_sr"`
	MaxSR           int16     `db:"max_sr"`
	MinGR           int16     `db:"min_gr"`
	MaxGR           int16     `db:"max_gr"`
	EventName       string    `db:"event_name"`
	Description     string    `db:"description"`
	Selection       bool      `db:"selection"`
}

func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfEnumerateDistItem)

	bf := byteframe.NewByteFrame()
	itemDists, err := s.server.distRepo.List(s.charID, pkt.DistType)
	if err != nil {
		s.logger.Error("Failed to list item distributions", zap.Error(err))
	}

	bf.WriteUint16(uint16(len(itemDists)))
	for _, dist := range itemDists {
		bf.WriteUint32(dist.ID)
		bf.WriteUint32(uint32(dist.Deadline.Unix()))
		bf.WriteUint32(dist.Rights)
		bf.WriteUint16(dist.TimesAcceptable)
		bf.WriteUint16(dist.TimesAccepted)
		if s.server.erupeConfig.RealClientMode >= cfg.G9 {
			bf.WriteUint16(0) // Unk
		}
		bf.WriteInt16(dist.MinHR)
		bf.WriteInt16(dist.MaxHR)
		bf.WriteInt16(dist.MinSR)
		bf.WriteInt16(dist.MaxSR)
		bf.WriteInt16(dist.MinGR)
		bf.WriteInt16(dist.MaxGR)
		if s.server.erupeConfig.RealClientMode >= cfg.G7 {
			bf.WriteUint8(0) // Unk
		}
		if s.server.erupeConfig.RealClientMode >= cfg.G6 {
			bf.WriteUint16(0) // Unk
		}
		if s.server.erupeConfig.RealClientMode >= cfg.G8 {
			if dist.Selection {
				bf.WriteUint8(2) // Selection
			} else {
				bf.WriteUint8(0)
			}
		}
		if s.server.erupeConfig.RealClientMode >= cfg.G7 {
			bf.WriteUint16(0) // Unk
			bf.WriteUint16(0) // Unk
		}
		if s.server.erupeConfig.RealClientMode >= cfg.G10 {
			bf.WriteUint8(0) // Unk
		}
		ps.Uint8(bf, dist.EventName, true)
		k := 6
		if s.server.erupeConfig.RealClientMode >= cfg.G8 {
			k = 13
		}
		for i := 0; i < 6; i++ {
			for j := 0; j < k; j++ {
				bf.WriteUint8(0)
				bf.WriteUint32(0)
			}
		}
		if s.server.erupeConfig.RealClientMode >= cfg.Z2 {
			i := uint8(0)
			bf.WriteUint8(i)
			if i <= 10 {
				for j := uint8(0); j < i; j++ {
					bf.WriteUint32(0)
					bf.WriteUint32(0)
					bf.WriteUint32(0)
				}
			}
		}
	}
	doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

// DistributionItem represents a single item in a distribution.
type DistributionItem struct {
	ItemType uint8  `db:"item_type"`
	ID       uint32 `db:"id"`
	ItemID   uint32 `db:"item_id"`
	Quantity uint32 `db:"quantity"`
}

func handleMsgMhfApplyDistItem(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfApplyDistItem)
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(pkt.DistributionID)
	distItems, err := s.server.distRepo.GetItems(pkt.DistributionID)
	if err != nil {
		s.logger.Error("Failed to get distribution items", zap.Error(err))
	}
	bf.WriteUint16(uint16(len(distItems)))
	for _, item := range distItems {
		bf.WriteUint8(item.ItemType)
		bf.WriteUint32(item.ItemID)
		bf.WriteUint32(item.Quantity)
		if s.server.erupeConfig.RealClientMode >= cfg.G8 {
			bf.WriteUint32(item.ID)
		}
	}
	doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

func handleMsgMhfAcquireDistItem(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfAcquireDistItem)
	if pkt.DistributionID > 0 {
		err := s.server.distRepo.RecordAccepted(pkt.DistributionID, s.charID)
		if err == nil {
			distItems, err := s.server.distRepo.GetItems(pkt.DistributionID)
			if err != nil {
				s.logger.Error("Failed to get distribution items for acquisition", zap.Error(err))
			}
			for _, item := range distItems {
				switch item.ItemType {
				case 17:
					if err := addPointNetcafe(s, int(item.Quantity)); err != nil {
						s.logger.Error("Failed to add dist item netcafe points", zap.Error(err))
					}
				case 19:
					if err := s.server.userRepo.AddPremiumCoins(s.userID, item.Quantity); err != nil {
						s.logger.Error("Failed to update gacha premium", zap.Error(err))
					}
				case 20:
					if err := s.server.userRepo.AddTrialCoins(s.userID, item.Quantity); err != nil {
						s.logger.Error("Failed to update gacha trial", zap.Error(err))
					}
				case 21:
					if err := s.server.userRepo.AddFrontierPoints(s.userID, item.Quantity); err != nil {
						s.logger.Error("Failed to update frontier points", zap.Error(err))
					}
				case 23:
					saveData, err := GetCharacterSaveData(s, s.charID)
					if err == nil {
						saveData.RP += uint16(item.Quantity)
						if err := saveData.Save(s); err != nil {
							s.logger.Error("Failed to save RP from dist item", zap.Error(err))
						}
					}
				}
			}
		}
	}
	doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

func handleMsgMhfGetDistDescription(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfGetDistDescription)
	desc, err := s.server.distRepo.GetDescription(pkt.DistributionID)
	if err != nil {
		s.logger.Error("Error parsing item distribution description", zap.Error(err))
		doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
		return
	}
	bf := byteframe.NewByteFrame()
	ps.Uint16(bf, desc, true)
	ps.Uint16(bf, "", false)
	doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
