package postgres

import (
	"context"
	"sync"
	"testing"
	"time"

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"github.com/break/junhong_cmp_fiber/tests/testutils"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestDeviceSimBindingStore_Create_DuplicateCard(t *testing.T) {
	tx := testutils.NewTestTransaction(t)
	rdb := testutils.GetTestRedis(t)
	testutils.CleanTestRedisKeys(t, rdb)

	bindingStore := NewDeviceSimBindingStore(tx, rdb)
	deviceStore := NewDeviceStore(tx, rdb)
	cardStore := NewIotCardStore(tx, rdb)
	ctx := context.Background()

	device1 := &model.Device{DeviceNo: "TEST-DEV-UC-001", Status: 1, MaxSimSlots: 4}
	device2 := &model.Device{DeviceNo: "TEST-DEV-UC-002", Status: 1, MaxSimSlots: 4}
	require.NoError(t, deviceStore.Create(ctx, device1))
	require.NoError(t, deviceStore.Create(ctx, device2))

	card := &model.IotCard{ICCID: "89860012345678910001", CardType: "data_card", CarrierID: 1, Status: 1}
	require.NoError(t, cardStore.Create(ctx, card))

	now := time.Now()
	binding1 := &model.DeviceSimBinding{
		DeviceID:     device1.ID,
		IotCardID:    card.ID,
		SlotPosition: 1,
		BindStatus:   1,
		BindTime:     &now,
	}
	require.NoError(t, bindingStore.Create(ctx, binding1))

	binding2 := &model.DeviceSimBinding{
		DeviceID:     device2.ID,
		IotCardID:    card.ID,
		SlotPosition: 1,
		BindStatus:   1,
		BindTime:     &now,
	}
	err := bindingStore.Create(ctx, binding2)
	require.Error(t, err)

	appErr, ok := err.(*errors.AppError)
	require.True(t, ok, "错误应该是 AppError 类型")
	assert.Equal(t, errors.CodeIotCardBoundToDevice, appErr.Code)
	assert.Contains(t, appErr.Message, "该卡已绑定到其他设备")
}

func TestDeviceSimBindingStore_Create_DuplicateSlot(t *testing.T) {
	tx := testutils.NewTestTransaction(t)
	rdb := testutils.GetTestRedis(t)
	testutils.CleanTestRedisKeys(t, rdb)

	bindingStore := NewDeviceSimBindingStore(tx, rdb)
	deviceStore := NewDeviceStore(tx, rdb)
	cardStore := NewIotCardStore(tx, rdb)
	ctx := context.Background()

	device := &model.Device{DeviceNo: "TEST-DEV-UC-003", Status: 1, MaxSimSlots: 4}
	require.NoError(t, deviceStore.Create(ctx, device))

	card1 := &model.IotCard{ICCID: "89860012345678910011", CardType: "data_card", CarrierID: 1, Status: 1}
	card2 := &model.IotCard{ICCID: "89860012345678910012", CardType: "data_card", CarrierID: 1, Status: 1}
	require.NoError(t, cardStore.Create(ctx, card1))
	require.NoError(t, cardStore.Create(ctx, card2))

	now := time.Now()
	binding1 := &model.DeviceSimBinding{
		DeviceID:     device.ID,
		IotCardID:    card1.ID,
		SlotPosition: 1,
		BindStatus:   1,
		BindTime:     &now,
	}
	require.NoError(t, bindingStore.Create(ctx, binding1))

	binding2 := &model.DeviceSimBinding{
		DeviceID:     device.ID,
		IotCardID:    card2.ID,
		SlotPosition: 1,
		BindStatus:   1,
		BindTime:     &now,
	}
	err := bindingStore.Create(ctx, binding2)
	require.Error(t, err)

	appErr, ok := err.(*errors.AppError)
	require.True(t, ok, "错误应该是 AppError 类型")
	assert.Equal(t, errors.CodeConflict, appErr.Code)
	assert.Contains(t, appErr.Message, "该插槽已有绑定的卡")
}

func TestDeviceSimBindingStore_Create_DifferentSlots(t *testing.T) {
	tx := testutils.NewTestTransaction(t)
	rdb := testutils.GetTestRedis(t)
	testutils.CleanTestRedisKeys(t, rdb)

	bindingStore := NewDeviceSimBindingStore(tx, rdb)
	deviceStore := NewDeviceStore(tx, rdb)
	cardStore := NewIotCardStore(tx, rdb)
	ctx := context.Background()

	device := &model.Device{DeviceNo: "TEST-DEV-UC-004", Status: 1, MaxSimSlots: 4}
	require.NoError(t, deviceStore.Create(ctx, device))

	card1 := &model.IotCard{ICCID: "89860012345678910021", CardType: "data_card", CarrierID: 1, Status: 1}
	card2 := &model.IotCard{ICCID: "89860012345678910022", CardType: "data_card", CarrierID: 1, Status: 1}
	require.NoError(t, cardStore.Create(ctx, card1))
	require.NoError(t, cardStore.Create(ctx, card2))

	now := time.Now()
	binding1 := &model.DeviceSimBinding{
		DeviceID:     device.ID,
		IotCardID:    card1.ID,
		SlotPosition: 1,
		BindStatus:   1,
		BindTime:     &now,
	}
	require.NoError(t, bindingStore.Create(ctx, binding1))
	assert.NotZero(t, binding1.ID)

	binding2 := &model.DeviceSimBinding{
		DeviceID:     device.ID,
		IotCardID:    card2.ID,
		SlotPosition: 2,
		BindStatus:   1,
		BindTime:     &now,
	}
	err := bindingStore.Create(ctx, binding2)
	require.NoError(t, err)
	assert.NotZero(t, binding2.ID)
}

func TestDeviceSimBindingStore_ConcurrentBinding(t *testing.T) {
	db := testutils.GetTestDB(t)
	rdb := testutils.GetTestRedis(t)
	testutils.CleanTestRedisKeys(t, rdb)

	deviceStore := NewDeviceStore(db, rdb)
	cardStore := NewIotCardStore(db, rdb)
	ctx := context.Background()

	device1 := &model.Device{DeviceNo: "TEST-CONCURRENT-001", Status: 1, MaxSimSlots: 4}
	device2 := &model.Device{DeviceNo: "TEST-CONCURRENT-002", Status: 1, MaxSimSlots: 4}
	require.NoError(t, deviceStore.Create(ctx, device1))
	require.NoError(t, deviceStore.Create(ctx, device2))

	card := &model.IotCard{ICCID: "89860012345678920001", CardType: "data_card", CarrierID: 1, Status: 1}
	require.NoError(t, cardStore.Create(ctx, card))

	t.Cleanup(func() {
		db.Where("device_id IN ?", []uint{device1.ID, device2.ID}).Delete(&model.DeviceSimBinding{})
		db.Delete(device1)
		db.Delete(device2)
		db.Delete(card)
	})

	t.Run("并发绑定同一张卡到不同设备", func(t *testing.T) {
		bindingStore := NewDeviceSimBindingStore(db, rdb)
		var wg sync.WaitGroup
		results := make(chan error, 2)

		for i, deviceID := range []uint{device1.ID, device2.ID} {
			wg.Add(1)
			go func(devID uint, slot int) {
				defer wg.Done()
				now := time.Now()
				binding := &model.DeviceSimBinding{
					DeviceID:     devID,
					IotCardID:    card.ID,
					SlotPosition: slot,
					BindStatus:   1,
					BindTime:     &now,
				}
				results <- bindingStore.Create(ctx, binding)
			}(deviceID, i+1)
		}

		wg.Wait()
		close(results)

		var successCount, errorCount int
		for err := range results {
			if err == nil {
				successCount++
			} else {
				errorCount++
				appErr, ok := err.(*errors.AppError)
				if ok {
					assert.Equal(t, errors.CodeIotCardBoundToDevice, appErr.Code)
				}
			}
		}

		assert.Equal(t, 1, successCount, "应该只有一个请求成功")
		assert.Equal(t, 1, errorCount, "应该有一个请求失败")
	})
}
