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/jackc/pgx/v5/pgconn"
	"github.com/redis/go-redis/v9"
	"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 bindings []struct {
		ICCID string
	}
	if err := s.db.WithContext(ctx).
		Table("tb_device_sim_binding b").
		Select("c.iccid").
		Joins("JOIN tb_iot_card c ON c.id = b.iot_card_id").
		Where("c.iccid IN ? AND b.bind_status = 1 AND c.deleted_at IS NULL", iccids).
		Find(&bindings).Error; err != nil {
		return nil, err
	}

	for _, b := range bindings {
		result[b.ICCID] = true
	}
	return result, nil
}
