package channelserver

import (
	"erupe-ce/common/byteframe"
	"erupe-ce/common/mhfcourse"
	ps "erupe-ce/common/pascalstring"
	cfg "erupe-ce/config"
	"erupe-ce/network/mhfpacket"
	"fmt"
	"go.uber.org/zap"
	"io"
	"time"
)

func handleMsgMhfAcquireCafeItem(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfAcquireCafeItem)
	netcafePoints, err := adjustCharacterInt(s, "netcafe_points", -int(pkt.PointCost))
	if err != nil {
		s.logger.Error("Failed to deduct netcafe points", zap.Error(err))
	}
	resp := byteframe.NewByteFrame()
	resp.WriteUint32(uint32(netcafePoints))
	doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
}

func handleMsgMhfUpdateCafepoint(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfUpdateCafepoint)
	netcafePoints, err := readCharacterInt(s, "netcafe_points")
	if err != nil {
		s.logger.Error("Failed to get netcafe points", zap.Error(err))
	}
	resp := byteframe.NewByteFrame()
	resp.WriteUint32(uint32(netcafePoints))
	doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
}

func handleMsgMhfCheckDailyCafepoint(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfCheckDailyCafepoint)

	midday := TimeMidnight().Add(12 * time.Hour)
	if TimeAdjusted().After(midday) {
		midday = midday.Add(24 * time.Hour)
	}

	// get time after which daily claiming would be valid from db
	dailyTime, err := s.server.charRepo.ReadTime(s.charID, "daily_time", time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))
	if err != nil {
		s.logger.Error("Failed to get daily_time savedata from db", zap.Error(err))
	}

	var bondBonus, bonusQuests, dailyQuests uint32
	bf := byteframe.NewByteFrame()
	if midday.After(dailyTime) {
		if err := addPointNetcafe(s, 5); err != nil {
			s.logger.Error("Failed to add daily netcafe points", zap.Error(err))
		}
		bondBonus = 5 // Bond point bonus quests
		bonusQuests = s.server.erupeConfig.GameplayOptions.BonusQuestAllowance
		dailyQuests = s.server.erupeConfig.GameplayOptions.DailyQuestAllowance
		if err := s.server.charRepo.UpdateDailyCafe(s.charID, midday, bonusQuests, dailyQuests); err != nil {
			s.logger.Error("Failed to update daily cafe data", zap.Error(err))
		}
		bf.WriteBool(true) // Success?
	} else {
		bf.WriteBool(false)
	}
	bf.WriteUint32(bondBonus)
	bf.WriteUint32(bonusQuests)
	bf.WriteUint32(dailyQuests)
	doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfGetCafeDuration)
	bf := byteframe.NewByteFrame()

	cafeReset, err := s.server.charRepo.ReadTime(s.charID, "cafe_reset", time.Time{})
	if err != nil {
		cafeReset = TimeWeekNext()
		if err := s.server.charRepo.SaveTime(s.charID, "cafe_reset", cafeReset); err != nil {
			s.logger.Error("Failed to set cafe reset time", zap.Error(err))
		}
	}
	if TimeAdjusted().After(cafeReset) {
		cafeReset = TimeWeekNext()
		if err := s.server.charRepo.ResetCafeTime(s.charID, cafeReset); err != nil {
			s.logger.Error("Failed to reset cafe time", zap.Error(err))
		}
		if err := s.server.cafeRepo.ResetAccepted(s.charID); err != nil {
			s.logger.Error("Failed to delete accepted cafe bonuses", zap.Error(err))
		}
	}

	cafeTime, err := readCharacterInt(s, "cafe_time")
	if err != nil {
		s.logger.Error("Failed to get cafe time", zap.Error(err))
		doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
		return
	}
	if mhfcourse.CourseExists(30, s.courses) {
		cafeTime = int(TimeAdjusted().Unix()) - int(s.sessionStart) + cafeTime
	}
	bf.WriteUint32(uint32(cafeTime))
	if s.server.erupeConfig.RealClientMode >= cfg.ZZ {
		bf.WriteUint16(0)
		ps.Uint16(bf, fmt.Sprintf(s.I18n().cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true)
	}
	doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

// CafeBonus represents a cafe duration bonus reward entry.
type CafeBonus struct {
	ID       uint32 `db:"id"`
	TimeReq  uint32 `db:"time_req"`
	ItemType uint32 `db:"item_type"`
	ItemID   uint32 `db:"item_id"`
	Quantity uint32 `db:"quantity"`
	Claimed  bool   `db:"claimed"`
}

func handleMsgMhfGetCafeDurationBonusInfo(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfGetCafeDurationBonusInfo)

	bonuses, err := s.server.cafeRepo.GetBonuses(s.charID)
	if err != nil {
		s.logger.Error("Error getting cafebonus", zap.Error(err))
		doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
		return
	}
	bf := byteframe.NewByteFrame()
	for _, cb := range bonuses {
		bf.WriteUint32(cb.TimeReq)
		bf.WriteUint32(cb.ItemType)
		bf.WriteUint32(cb.ItemID)
		bf.WriteUint32(cb.Quantity)
		bf.WriteBool(cb.Claimed)
	}
	resp := byteframe.NewByteFrame()
	resp.WriteUint32(0)
	resp.WriteUint32(uint32(TimeAdjusted().Unix()))
	resp.WriteUint32(uint32(len(bonuses)))
	resp.WriteBytes(bf.Data())
	doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}

func handleMsgMhfReceiveCafeDurationBonus(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfReceiveCafeDurationBonus)
	bf := byteframe.NewByteFrame()
	bf.WriteUint32(0)
	claimable, err := s.server.cafeRepo.GetClaimable(s.charID, TimeAdjusted().Unix()-s.sessionStart)
	if err != nil || !mhfcourse.CourseExists(30, s.courses) {
		doAckBufSucceed(s, pkt.AckHandle, bf.Data())
	} else {
		for _, cb := range claimable {
			bf.WriteUint32(cb.ID)
			bf.WriteUint32(cb.ItemType)
			bf.WriteUint32(cb.ItemID)
			bf.WriteUint32(cb.Quantity)
		}
		_, _ = bf.Seek(0, io.SeekStart)
		bf.WriteUint32(uint32(len(claimable)))
		doAckBufSucceed(s, pkt.AckHandle, bf.Data())
	}
}

func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfPostCafeDurationBonusReceived)
	for _, cbID := range pkt.CafeBonusID {
		itemType, quantity, err := s.server.cafeRepo.GetBonusItem(cbID)
		if err == nil {
			if itemType == 17 {
				if err := addPointNetcafe(s, int(quantity)); err != nil {
					s.logger.Error("Failed to add cafe bonus netcafe points", zap.Error(err))
				}
			}
		}
		if err := s.server.cafeRepo.AcceptBonus(cbID, s.charID); err != nil {
			s.logger.Error("Failed to insert accepted cafe bonus", zap.Error(err))
		}
	}
	doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

func addPointNetcafe(s *Session, p int) error {
	points, err := readCharacterInt(s, "netcafe_points")
	if err != nil {
		return err
	}
	points = min(points+p, s.server.erupeConfig.GameplayOptions.MaximumNP)
	if err := s.server.charRepo.SaveInt(s.charID, "netcafe_points", points); err != nil {
		s.logger.Error("Failed to update netcafe points", zap.Error(err))
		return fmt.Errorf("save netcafe points: %w", err)
	}
	return nil
}

func handleMsgMhfStartBoostTime(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfStartBoostTime)
	bf := byteframe.NewByteFrame()
	boostLimit := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.BoostTimeDuration) * time.Second)
	if s.server.erupeConfig.GameplayOptions.DisableBoostTime {
		bf.WriteUint32(0)
		doAckBufSucceed(s, pkt.AckHandle, bf.Data())
		return
	}
	if err := s.server.charRepo.SaveTime(s.charID, "boost_time", boostLimit); err != nil {
		s.logger.Error("Failed to update boost time", zap.Error(err))
	}
	bf.WriteUint32(uint32(boostLimit.Unix()))
	doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

func handleMsgMhfGetBoostTime(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfGetBoostTime)
	doAckBufSucceed(s, pkt.AckHandle, []byte{})
}

func handleMsgMhfGetBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfGetBoostTimeLimit)
	bf := byteframe.NewByteFrame()
	boostLimit, err := s.server.charRepo.ReadTime(s.charID, "boost_time", time.Time{})
	// Return 0 when disabled, on read error, when boost_time is unset,
	// or when boost_time is already in the past. A naked uint32() cast
	// on a pre-1970 int64 wraps to a huge future-looking timestamp the
	// client interprets as permanently active (see the year-1906 rows
	// left behind by the pre-#187 bug). Harmonise with GetBoostRight
	// so the two handlers always agree on the same row.
	if err != nil || s.server.erupeConfig.GameplayOptions.DisableBoostTime ||
		boostLimit.IsZero() || !boostLimit.After(TimeAdjusted()) {
		bf.WriteUint32(0)
	} else {
		bf.WriteUint32(uint32(boostLimit.Unix()))
	}
	doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

func handleMsgMhfGetBoostRight(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfGetBoostRight)
	if s.server.erupeConfig.GameplayOptions.DisableBoostTime {
		doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
		return
	}
	boostLimit, err := s.server.charRepo.ReadTime(s.charID, "boost_time", time.Time{})
	if err != nil {
		doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
		return
	}
	if boostLimit.After(TimeAdjusted()) {
		doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
	} else {
		doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x02})
	}
}

func handleMsgMhfPostBoostTimeQuestReturn(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfPostBoostTimeQuestReturn)
	doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}

func handleMsgMhfPostBoostTime(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfPostBoostTime)
	doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

func handleMsgMhfPostBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {
	pkt := p.(*mhfpacket.MsgMhfPostBoostTimeLimit)
	doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
