package service

import (
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"strings"
	"time"

	"github.com/mhsanaei/3x-ui/v3/internal/database"
	"github.com/mhsanaei/3x-ui/v3/internal/database/model"

	"gorm.io/gorm"
)

type HwidRequest struct {
	Hwid        string
	UserAgent   string
	DeviceOS    string
	OsVersion   string
	DeviceModel string
}

type HwidGateResult struct {
	Allowed           bool
	Active            bool
	NotSupported      bool
	MaxDevicesReached bool
	LimitReached      bool
	Limit             int
	Registered        int
}

const minHwidLength = 6

type ClientHwidInfo struct {
	Id          int    `json:"id"`
	FirstSeen   int64  `json:"firstSeen"`
	LastSeen    int64  `json:"lastSeen"`
	UserAgent   string `json:"userAgent"`
	DeviceOS    string `json:"deviceOs"`
	OsVersion   string `json:"osVersion"`
	DeviceModel string `json:"deviceModel"`
}

func hashHwid(raw string) string {
	sum := sha256.Sum256([]byte(raw))
	return hex.EncodeToString(sum[:])
}

func trimHwidMeta(s string) string {
	s = strings.TrimSpace(s)
	r := []rune(s)
	if len(r) > 512 {
		return string(r[:512])
	}
	return s
}

func normalizeHwidRequest(req HwidRequest) HwidRequest {
	return HwidRequest{
		Hwid:        strings.TrimSpace(req.Hwid),
		UserAgent:   trimHwidMeta(req.UserAgent),
		DeviceOS:    trimHwidMeta(req.DeviceOS),
		OsVersion:   trimHwidMeta(req.OsVersion),
		DeviceModel: trimHwidMeta(req.DeviceModel),
	}
}

func effectiveHwidLimitForSubID(tx *gorm.DB, subID string) (int, error) {
	var limit int
	err := tx.Model(&model.ClientRecord{}).
		Where("sub_id = ? AND enable = ?", subID, true).
		Select("COALESCE(MAX(limit_hwid), 0)").
		Scan(&limit).Error
	return limit, err
}

func (s *ClientService) EnforceHwidForSubID(subID string, req HwidRequest) (HwidGateResult, error) {
	var res HwidGateResult
	subID = strings.TrimSpace(subID)
	if subID == "" {
		res.Allowed = true
		return res, nil
	}

	db := database.GetDB()
	limit, err := effectiveHwidLimitForSubID(db, subID)
	if err != nil {
		return res, err
	}
	if limit <= 0 {
		res.Allowed = true
		return res, nil
	}

	req = normalizeHwidRequest(req)
	res.Active = true
	res.Limit = limit
	if len(req.Hwid) < minHwidLength {
		res.NotSupported = true
		return res, nil
	}
	hwidHash := hashHwid(req.Hwid)

	err = db.Transaction(func(tx *gorm.DB) error {
		limit, err := effectiveHwidLimitForSubID(tx, subID)
		if err != nil {
			return err
		}
		if limit <= 0 {
			res = HwidGateResult{Allowed: true}
			return nil
		}
		res.Active = true
		res.Limit = limit
		now := time.Now().UnixMilli()
		var existing model.ClientHwid
		err = tx.Where("sub_id = ? AND hwid_hash = ?", subID, hwidHash).First(&existing).Error
		if err == nil {
			if err := tx.Model(&model.ClientHwid{}).Where("id = ?", existing.Id).Updates(map[string]any{
				"last_seen": now, "user_agent": req.UserAgent, "device_os": req.DeviceOS, "os_version": req.OsVersion, "device_model": req.DeviceModel,
			}).Error; err != nil {
				return err
			}
			var count int64
			if err := tx.Model(&model.ClientHwid{}).Where("sub_id = ?", subID).Count(&count).Error; err != nil {
				return err
			}
			res.Allowed = true
			res.Registered = int(count)
			res.LimitReached = count >= int64(limit)
			return nil
		}
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			return err
		}
		var count int64
		if err := tx.Model(&model.ClientHwid{}).Where("sub_id = ?", subID).Count(&count).Error; err != nil {
			return err
		}
		res.Registered = int(count)
		if count >= int64(limit) {
			res.MaxDevicesReached = true
			res.LimitReached = true
			return nil
		}
		if err := tx.Create(&model.ClientHwid{SubID: subID, HwidHash: hwidHash, FirstSeen: now, LastSeen: now, UserAgent: req.UserAgent, DeviceOS: req.DeviceOS, OsVersion: req.OsVersion, DeviceModel: req.DeviceModel}).Error; err != nil {
			return err
		}
		res.Allowed = true
		res.Registered = int(count) + 1
		res.LimitReached = res.Registered >= limit
		return nil
	})
	return res, err
}

func (s *ClientService) ListClientHwids(email string) ([]ClientHwidInfo, error) {
	rec, err := s.GetRecordByEmail(nil, email)
	if err != nil {
		return nil, err
	}
	subID := strings.TrimSpace(rec.SubID)
	if subID == "" {
		return nil, nil
	}
	var rows []model.ClientHwid
	if err := database.GetDB().
		Where("sub_id = ?", subID).
		Order("last_seen DESC").
		Order("id DESC").
		Find(&rows).Error; err != nil {
		return nil, err
	}
	out := make([]ClientHwidInfo, 0, len(rows))
	for _, r := range rows {
		out = append(out, ClientHwidInfo{
			Id:          r.Id,
			FirstSeen:   r.FirstSeen,
			LastSeen:    r.LastSeen,
			UserAgent:   r.UserAgent,
			DeviceOS:    r.DeviceOS,
			OsVersion:   r.OsVersion,
			DeviceModel: r.DeviceModel,
		})
	}
	return out, nil
}

func (s *ClientService) ClearClientHwids(email string) error {
	rec, err := s.GetRecordByEmail(nil, email)
	if err != nil {
		return err
	}
	subID := strings.TrimSpace(rec.SubID)
	if subID == "" {
		return nil
	}
	return database.GetDB().Where("sub_id = ?", subID).Delete(&model.ClientHwid{}).Error
}

func (s *ClientService) setClientLimitHwidByEmail(tx *gorm.DB, email string, limit int) error {
	if tx == nil {
		tx = database.GetDB()
	}
	if limit < 0 {
		limit = 0
	}
	var rec model.ClientRecord
	if err := tx.Where("email = ?", email).First(&rec).Error; err != nil {
		return err
	}
	if err := tx.Model(&model.ClientRecord{}).Where("id = ?", rec.Id).UpdateColumn("limit_hwid", limit).Error; err != nil {
		return err
	}
	subID := strings.TrimSpace(rec.SubID)
	if subID == "" {
		return nil
	}
	effective, err := effectiveHwidLimitForSubID(tx, subID)
	if err != nil {
		return err
	}
	return trimClientHwidsForSubID(tx, subID, effective)
}

func trimClientHwidsForSubID(tx *gorm.DB, subID string, limit int) error {
	subID = strings.TrimSpace(subID)
	if subID == "" || limit <= 0 {
		return nil
	}
	var keep []int
	if err := tx.Model(&model.ClientHwid{}).
		Where("sub_id = ?", subID).
		Order("last_seen DESC").
		Order("id DESC").
		Limit(limit).
		Pluck("id", &keep).Error; err != nil {
		return err
	}
	if len(keep) == 0 {
		return tx.Where("sub_id = ?", subID).Delete(&model.ClientHwid{}).Error
	}
	return tx.Where("sub_id = ? AND id NOT IN ?", subID, keep).Delete(&model.ClientHwid{}).Error
}

func clearClientHwidsBySubIDTx(tx *gorm.DB, subIDs ...string) error {
	if tx == nil {
		tx = database.GetDB()
	}
	clean := make([]string, 0, len(subIDs))
	seen := map[string]struct{}{}
	for _, subID := range subIDs {
		subID = strings.TrimSpace(subID)
		if subID == "" {
			continue
		}
		if _, ok := seen[subID]; ok {
			continue
		}
		seen[subID] = struct{}{}
		clean = append(clean, subID)
	}
	for _, batch := range chunkStrings(clean, sqlInChunk) {
		if err := tx.Where("sub_id IN ?", batch).Delete(&model.ClientHwid{}).Error; err != nil {
			return err
		}
	}
	return nil
}
