package postgres

import (
	"context"
	stderrors "errors"
	"strings"
	"time"

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"github.com/break/junhong_cmp_fiber/pkg/logger"
	"github.com/jackc/pgx/v5/pgconn"
	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type DeviceSimBindingStore struct {
	db    *gorm.DB
	redis *redis.Client
}

func NewDeviceSimBindingStore(db *gorm.DB, redis *redis.Client) *DeviceSimBindingStore {
	return &DeviceSimBindingStore{
		db:    db,
		redis: redis,
	}
}

func (s *DeviceSimBindingStore) Create(ctx context.Context, binding *model.DeviceSimBinding) error {
	err := s.db.WithContext(ctx).Create(binding).Error
	if err != nil {
		if isUniqueViolation(err) {
			if strings.Contains(err.Error(), "idx_active_device_slot") {
				return errors.New(errors.CodeConflict, "该插槽已有绑定的卡")
			}
			if strings.Contains(err.Error(), "idx_device_sim_bindings_active_card") {
				return errors.New(errors.CodeIotCardBoundToDevice, "该卡已绑定到其他设备")
			}
		}
		return err
	}
	return nil
}

func isUniqueViolation(err error) bool {
	var pgErr *pgconn.PgError
	if stderrors.As(err, &pgErr) {
		return pgErr.Code == "23505"
	}
	return false
}

func (s *DeviceSimBindingStore) CreateBatch(ctx context.Context, bindings []*model.DeviceSimBinding) error {
	if len(bindings) == 0 {
		return nil
	}
	return s.db.WithContext(ctx).CreateInBatches(bindings, 100).Error
}

func (s *DeviceSimBindingStore) GetByID(ctx context.Context, id uint) (*model.DeviceSimBinding, error) {
	var binding model.DeviceSimBinding
	if err := s.db.WithContext(ctx).First(&binding, id).Error; err != nil {
		return nil, err
	}
	return &binding, nil
}

func (s *DeviceSimBindingStore) ListByDeviceID(ctx context.Context, deviceID uint) ([]*model.DeviceSimBinding, error) {
	var bindings []*model.DeviceSimBinding
	if err := s.db.WithContext(ctx).
		Where("device_id = ? AND bind_status = 1", deviceID).
		Order("slot_position ASC").
		Find(&bindings).Error; err != nil {
		return nil, err
	}
	return bindings, nil
}

func (s *DeviceSimBindingStore) ListByDeviceIDs(ctx context.Context, deviceIDs []uint) ([]*model.DeviceSimBinding, error) {
	var bindings []*model.DeviceSimBinding
	if len(deviceIDs) == 0 {
		return bindings, nil
	}
	if err := s.db.WithContext(ctx).
		Where("device_id IN ? AND bind_status = 1", deviceIDs).
		Find(&bindings).Error; err != nil {
		return nil, err
	}
	return bindings, nil
}

func (s *DeviceSimBindingStore) GetByDeviceAndCard(ctx context.Context, deviceID, iotCardID uint) (*model.DeviceSimBinding, error) {
	var binding model.DeviceSimBinding
	if err := s.db.WithContext(ctx).
		Where("device_id = ? AND iot_card_id = ? AND bind_status = 1", deviceID, iotCardID).
		First(&binding).Error; err != nil {
		return nil, err
	}
	return &binding, nil
}

func (s *DeviceSimBindingStore) GetByDeviceAndSlot(ctx context.Context, deviceID uint, slotPosition int) (*model.DeviceSimBinding, error) {
	var binding model.DeviceSimBinding
	if err := s.db.WithContext(ctx).
		Where("device_id = ? AND slot_position = ? AND bind_status = 1", deviceID, slotPosition).
		First(&binding).Error; err != nil {
		return nil, err
	}
	return &binding, nil
}

func (s *DeviceSimBindingStore) GetActiveBindingByCardID(ctx context.Context, iotCardID uint) (*model.DeviceSimBinding, error) {
	var binding model.DeviceSimBinding
	if err := s.db.WithContext(ctx).
		Where("iot_card_id = ? AND bind_status = 1", iotCardID).
		First(&binding).Error; err != nil {
		return nil, err
	}
	return &binding, nil
}

func (s *DeviceSimBindingStore) GetActiveBindingsByCardIDs(ctx context.Context, iotCardIDs []uint) ([]*model.DeviceSimBinding, error) {
	var bindings []*model.DeviceSimBinding
	if len(iotCardIDs) == 0 {
		return bindings, nil
	}
	if err := s.db.WithContext(ctx).
		Where("iot_card_id IN ? AND bind_status = 1", iotCardIDs).
		Find(&bindings).Error; err != nil {
		return nil, err
	}
	return bindings, nil
}

func (s *DeviceSimBindingStore) Unbind(ctx context.Context, id uint) error {
	now := time.Now()
	updates := map[string]any{
		"bind_status": 2,
		"unbind_time": now,
		"updated_at":  now,
	}
	return s.db.WithContext(ctx).Model(&model.DeviceSimBinding{}).Where("id = ?", id).Updates(updates).Error
}

func (s *DeviceSimBindingStore) UnbindByDeviceID(ctx context.Context, deviceID uint) error {
	now := time.Now()
	updates := map[string]any{
		"bind_status": 2,
		"unbind_time": now,
		"updated_at":  now,
	}
	return s.db.WithContext(ctx).Model(&model.DeviceSimBinding{}).
		Where("device_id = ? AND bind_status = 1", deviceID).
		Updates(updates).Error
}

func (s *DeviceSimBindingStore) CountByDeviceID(ctx context.Context, deviceID uint) (int64, error) {
	var count int64
	if err := s.db.WithContext(ctx).Model(&model.DeviceSimBinding{}).
		Where("device_id = ? AND bind_status = 1", deviceID).
		Count(&count).Error; err != nil {
		return 0, err
	}
	return count, nil
}

func (s *DeviceSimBindingStore) GetBoundCardIDsByDeviceIDs(ctx context.Context, deviceIDs []uint) ([]uint, error) {
	if len(deviceIDs) == 0 {
		return nil, nil
	}
	var cardIDs []uint
	if err := s.db.WithContext(ctx).Model(&model.DeviceSimBinding{}).
		Select("iot_card_id").
		Where("device_id IN ? AND bind_status = 1", deviceIDs).
		Pluck("iot_card_id", &cardIDs).Error; err != nil {
		return nil, err
	}
	return cardIDs, nil
}

func (s *DeviceSimBindingStore) GetBoundICCIDs(ctx context.Context, iccids []string) (map[string]bool, error) {
	result := make(map[string]bool)
	if len(iccids) == 0 {
		return result, nil
	}

	var list19, list20 []string
	for _, id := range iccids {
		switch len(id) {
		case 19:
			list19 = append(list19, id)
		case 20:
			list20 = append(list20, id)
		}
	}

	if len(list19) > 0 {
		var rows []struct{ ICCID string }
		if err := s.db.WithContext(ctx).
			Table("tb_device_sim_binding b").
			Select("c.iccid_19 AS iccid").
			Joins("JOIN tb_iot_card c ON c.id = b.iot_card_id").
			Where("c.iccid_19 IN ? AND b.bind_status = 1 AND c.deleted_at IS NULL", list19).
			Find(&rows).Error; err != nil {
			return nil, err
		}
		for _, r := range rows {
			result[r.ICCID] = true
		}
	}

	if len(list20) > 0 {
		var rows []struct{ ICCID string }
		if err := s.db.WithContext(ctx).
			Table("tb_device_sim_binding b").
			Select("c.iccid_20 AS iccid").
			Joins("JOIN tb_iot_card c ON c.id = b.iot_card_id").
			Where("c.iccid_20 IN ? AND b.bind_status = 1 AND c.deleted_at IS NULL", list20).
			Find(&rows).Error; err != nil {
			return nil, err
		}
		for _, r := range rows {
			result[r.ICCID] = true
		}
	}

	return result, nil
}

// UpdateIsCurrentByDeviceID 原子更新设备当前使用的卡标识
// 使用事务两步操作：先全部设为 false，再将 currentSlotNumber 对应插槽设为 true。
// currentSlotNumber <= 0 时，仅执行全部清空（无当前卡的情况）。
func (s *DeviceSimBindingStore) UpdateIsCurrentByDeviceID(ctx context.Context, deviceID uint, currentSlotNumber int) error {
	return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.Model(&model.DeviceSimBinding{}).
			Where("device_id = ? AND bind_status = 1", deviceID).
			Update("is_current", false).Error; err != nil {
			return err
		}

		if currentSlotNumber <= 0 {
			return nil
		}

		result := tx.Model(&model.DeviceSimBinding{}).
			Where("device_id = ? AND slot_position = ? AND bind_status = 1", deviceID, currentSlotNumber).
			Update("is_current", true)
		if result.Error != nil {
			return result.Error
		}
		if result.RowsAffected == 0 {
			logger.GetAppLogger().Warn("UpdateIsCurrentByDeviceID: 未命中插槽绑定记录，is_current 不更新",
				zap.Uint("device_id", deviceID),
				zap.Int("current_slot_number", currentSlotNumber),
			)
		}
		return nil
	})
}
