package usecase

import (
	"backend/pkg/notification/config"
	"backend/pkg/notification/domain/entity"
	"backend/pkg/notification/domain/repository"
	"backend/pkg/notification/domain/usecase"
	"context"
	"errors"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

// mockSMSRepository 模擬 SMS Repository
type mockSMSRepository struct {
	sendFunc func(ctx context.Context, req repository.SMSMessageRequest) error
}

func (m *mockSMSRepository) SendSMS(ctx context.Context, req repository.SMSMessageRequest) error {
	if m.sendFunc != nil {
		return m.sendFunc(ctx, req)
	}
	return nil
}

// mockMailRepository 模擬 Mail Repository
type mockMailRepository struct {
	sendFunc func(ctx context.Context, req repository.MailReq) error
}

func (m *mockMailRepository) SendMail(ctx context.Context, req repository.MailReq) error {
	if m.sendFunc != nil {
		return m.sendFunc(ctx, req)
	}
	return nil
}

// mockHistoryRepository 模擬 History Repository
type mockHistoryRepository struct {
	histories []entity.DeliveryHistory
	attempts  map[string][]entity.DeliveryAttempt
}

func (m *mockHistoryRepository) CreateHistory(ctx context.Context, history *entity.DeliveryHistory) error {
	m.histories = append(m.histories, *history)
	return nil
}

func (m *mockHistoryRepository) UpdateHistory(ctx context.Context, history *entity.DeliveryHistory) error {
	for i := range m.histories {
		if m.histories[i].ID == history.ID {
			m.histories[i] = *history
			return nil
		}
	}
	return nil
}

func (m *mockHistoryRepository) GetHistory(ctx context.Context, id string) (*entity.DeliveryHistory, error) {
	for i := range m.histories {
		if m.histories[i].ID == id {
			return &m.histories[i], nil
		}
	}
	return nil, errors.New("not found")
}

func (m *mockHistoryRepository) AddAttempt(ctx context.Context, historyID string, attempt entity.DeliveryAttempt) error {
	if m.attempts == nil {
		m.attempts = make(map[string][]entity.DeliveryAttempt)
	}
	m.attempts[historyID] = append(m.attempts[historyID], attempt)
	return nil
}

func (m *mockHistoryRepository) ListHistory(ctx context.Context, filter repository.HistoryFilter) ([]*entity.DeliveryHistory, error) {
	var result []*entity.DeliveryHistory
	for i := range m.histories {
		result = append(result, &m.histories[i])
	}
	return result, nil
}

func TestDeliveryUseCase_SendEmail_Success(t *testing.T) {
	mockMail := &mockMailRepository{
		sendFunc: func(ctx context.Context, req repository.MailReq) error {
			return nil // 成功
		},
	}

	mockHistory := &mockHistoryRepository{}

	uc := MustDeliveryUseCase(DeliveryUseCaseParam{
		EmailProviders: []usecase.EmailProvider{
			{Sort: 1, Repo: mockMail},
		},
		DeliveryConfig: config.DeliveryConfig{
			MaxRetries:    3,
			InitialDelay:  10 * time.Millisecond,
			BackoffFactor: 2.0,
			MaxDelay:      100 * time.Millisecond,
			Timeout:       5 * time.Second,
			EnableHistory: true,
		},
		HistoryRepo: mockHistory,
	})

	ctx := context.Background()
	err := uc.SendEmail(ctx, usecase.MailReq{
		From:    "test@example.com",
		To:      []string{"user@example.com"},
		Subject: "Test",
		Body:    "<p>Test email</p>",
	})

	assert.NoError(t, err)
	// 驗證歷史記錄
	assert.Equal(t, 1, len(mockHistory.histories))
	assert.Equal(t, entity.DeliveryStatusSuccess, mockHistory.histories[0].Status)
}

func TestDeliveryUseCase_SendEmail_RetryAndSuccess(t *testing.T) {
	attemptCount := 0
	mockMail := &mockMailRepository{
		sendFunc: func(ctx context.Context, req repository.MailReq) error {
			attemptCount++
			if attemptCount < 3 {
				return errors.New("temporary error")
			}
			return nil // 第三次成功
		},
	}

	mockHistory := &mockHistoryRepository{}

	uc := MustDeliveryUseCase(DeliveryUseCaseParam{
		EmailProviders: []usecase.EmailProvider{
			{Sort: 1, Repo: mockMail},
		},
		DeliveryConfig: config.DeliveryConfig{
			MaxRetries:    3,
			InitialDelay:  10 * time.Millisecond,
			BackoffFactor: 2.0,
			MaxDelay:      100 * time.Millisecond,
			Timeout:       5 * time.Second,
			EnableHistory: true,
		},
		HistoryRepo: mockHistory,
	})

	ctx := context.Background()
	err := uc.SendEmail(ctx, usecase.MailReq{
		From:    "test@example.com",
		To:      []string{"user@example.com"},
		Subject: "Test",
		Body:    "<p>Test</p>",
	})

	assert.NoError(t, err)
	assert.Equal(t, 3, attemptCount) // 重試了 3 次
	assert.Equal(t, 1, len(mockHistory.histories))
	assert.Equal(t, entity.DeliveryStatusSuccess, mockHistory.histories[0].Status)
	assert.Equal(t, 3, mockHistory.histories[0].AttemptCount)
}

func TestDeliveryUseCase_SendEmail_AllRetries_Failed(t *testing.T) {
	mockMail := &mockMailRepository{
		sendFunc: func(ctx context.Context, req repository.MailReq) error {
			return errors.New("persistent error")
		},
	}

	mockHistory := &mockHistoryRepository{}

	uc := MustDeliveryUseCase(DeliveryUseCaseParam{
		EmailProviders: []usecase.EmailProvider{
			{Sort: 1, Repo: mockMail},
		},
		DeliveryConfig: config.DeliveryConfig{
			MaxRetries:    3,
			InitialDelay:  10 * time.Millisecond,
			BackoffFactor: 2.0,
			MaxDelay:      100 * time.Millisecond,
			Timeout:       5 * time.Second,
			EnableHistory: true,
		},
		HistoryRepo: mockHistory,
	})

	ctx := context.Background()
	err := uc.SendEmail(ctx, usecase.MailReq{
		From:    "test@example.com",
		To:      []string{"user@example.com"},
		Subject: "Test",
		Body:    "<p>Test</p>",
	})

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "Failed to send Email")
	assert.Equal(t, 1, len(mockHistory.histories))
	assert.Equal(t, entity.DeliveryStatusFailed, mockHistory.histories[0].Status)
	assert.Equal(t, 3, mockHistory.histories[0].AttemptCount) // 嘗試了 3 次
}

func TestDeliveryUseCase_SendEmail_Failover(t *testing.T) {
	mockMail1 := &mockMailRepository{
		sendFunc: func(ctx context.Context, req repository.MailReq) error {
			return errors.New("provider 1 failed")
		},
	}

	mockMail2 := &mockMailRepository{
		sendFunc: func(ctx context.Context, req repository.MailReq) error {
			return nil // 備援成功
		},
	}

	mockHistory := &mockHistoryRepository{}

	uc := MustDeliveryUseCase(DeliveryUseCaseParam{
		EmailProviders: []usecase.EmailProvider{
			{Sort: 1, Repo: mockMail1}, // 主要供應商
			{Sort: 2, Repo: mockMail2}, // 備援供應商
		},
		DeliveryConfig: config.DeliveryConfig{
			MaxRetries:    2,
			InitialDelay:  10 * time.Millisecond,
			BackoffFactor: 2.0,
			MaxDelay:      100 * time.Millisecond,
			Timeout:       5 * time.Second,
			EnableHistory: true,
		},
		HistoryRepo: mockHistory,
	})

	ctx := context.Background()
	err := uc.SendEmail(ctx, usecase.MailReq{
		From:    "test@example.com",
		To:      []string{"user@example.com"},
		Subject: "Test",
		Body:    "<p>Test</p>",
	})

	assert.NoError(t, err)
	// 驗證使用了備援供應商
	assert.Equal(t, 1, len(mockHistory.histories))
	assert.Equal(t, entity.DeliveryStatusSuccess, mockHistory.histories[0].Status)
	// 總共嘗試次數：provider1 重試 2 次 + provider2 成功 1 次 = 3 次
	assert.Equal(t, 3, mockHistory.histories[0].AttemptCount)
}

func TestDeliveryUseCase_SendSMS_Success(t *testing.T) {
	mockSMS := &mockSMSRepository{
		sendFunc: func(ctx context.Context, req repository.SMSMessageRequest) error {
			return nil
		},
	}

	mockHistory := &mockHistoryRepository{}

	uc := MustDeliveryUseCase(DeliveryUseCaseParam{
		SMSProviders: []usecase.SMSProvider{
			{Sort: 1, Repo: mockSMS},
		},
		DeliveryConfig: config.DeliveryConfig{
			MaxRetries:    3,
			InitialDelay:  10 * time.Millisecond,
			BackoffFactor: 2.0,
			MaxDelay:      100 * time.Millisecond,
			Timeout:       5 * time.Second,
			EnableHistory: true,
		},
		HistoryRepo: mockHistory,
	})

	ctx := context.Background()
	err := uc.SendMessage(ctx, usecase.SMSMessageRequest{
		PhoneNumber:    "+886912345678",
		RecipientName:  "Test User",
		MessageContent: "Your code: 123456",
	})

	assert.NoError(t, err)
	assert.Equal(t, 1, len(mockHistory.histories))
	assert.Equal(t, entity.DeliveryStatusSuccess, mockHistory.histories[0].Status)
}

func TestDeliveryUseCase_CalculateDelay(t *testing.T) {
	uc := &DeliveryUseCase{
		param: DeliveryUseCaseParam{
			DeliveryConfig: config.DeliveryConfig{
				InitialDelay:  100 * time.Millisecond,
				BackoffFactor: 2.0,
				MaxDelay:      1 * time.Second,
			},
		},
	}

	tests := []struct {
		name     string
		attempt  int
		expected time.Duration
	}{
		{
			name:     "第 0 次重試",
			attempt:  0,
			expected: 100 * time.Millisecond,
		},
		{
			name:     "第 1 次重試",
			attempt:  1,
			expected: 200 * time.Millisecond,
		},
		{
			name:     "第 2 次重試",
			attempt:  2,
			expected: 400 * time.Millisecond,
		},
		{
			name:     "第 3 次重試",
			attempt:  3,
			expected: 800 * time.Millisecond,
		},
		{
			name:     "第 10 次重試（達到 MaxDelay）",
			attempt:  10,
			expected: 1 * time.Second, // 受限於 MaxDelay
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			delay := uc.calculateDelay(tt.attempt)
			assert.Equal(t, tt.expected, delay)
		})
	}
}

func TestDeliveryUseCase_ContextCancellation(t *testing.T) {
	mockMail := &mockMailRepository{
		sendFunc: func(ctx context.Context, req repository.MailReq) error {
			// 模擬慢速操作
			time.Sleep(100 * time.Millisecond)
			return errors.New("should not reach here")
		},
	}

	uc := MustDeliveryUseCase(DeliveryUseCaseParam{
		EmailProviders: []usecase.EmailProvider{
			{Sort: 1, Repo: mockMail},
		},
		DeliveryConfig: config.DeliveryConfig{
			MaxRetries:    3,
			InitialDelay:  50 * time.Millisecond,
			BackoffFactor: 2.0,
			MaxDelay:      500 * time.Millisecond,
			Timeout:       1 * time.Second,
			EnableHistory: false,
		},
	})

	// 創建會被取消的 context
	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
	defer cancel()

	err := uc.SendEmail(ctx, usecase.MailReq{
		From:    "test@example.com",
		To:      []string{"user@example.com"},
		Subject: "Test",
		Body:    "<p>Test</p>",
	})

	assert.Error(t, err)
	assert.Equal(t, context.DeadlineExceeded, err)
}
