package service

import (
	"context"
	"testing"
	"time"
)

// =============================================================================
// SMS Provider Tests
// =============================================================================

// mockCacheForSMS implements cacheInterface for testing
type mockCacheForSMS struct{}

func (m *mockCacheForSMS) Get(ctx context.Context, key string) (interface{}, bool) {
	return nil, false
}

func (m *mockCacheForSMS) Set(ctx context.Context, key string, value interface{}, l1TTL, l2TTL time.Duration) error {
	return nil
}

func (m *mockCacheForSMS) Delete(ctx context.Context, key string) error {
	return nil
}

func TestNewAliyunSMSProvider(t *testing.T) {
	t.Run("incomplete config returns error", func(t *testing.T) {
		cfg := AliyunSMSConfig{}
		_, err := NewAliyunSMSProvider(cfg)
		if err == nil {
			t.Error("Expected error for incomplete config")
		}
	})

	t.Run("missing access key", func(t *testing.T) {
		cfg := AliyunSMSConfig{
			AccessKeySecret: "secret",
			SignName:        "sign",
			TemplateCode:    "template",
		}
		_, err := NewAliyunSMSProvider(cfg)
		if err == nil {
			t.Error("Expected error for missing access key")
		}
	})

	t.Run("missing sign name", func(t *testing.T) {
		cfg := AliyunSMSConfig{
			AccessKeyID:     "key",
			AccessKeySecret: "secret",
			TemplateCode:    "template",
		}
		_, err := NewAliyunSMSProvider(cfg)
		if err == nil {
			t.Error("Expected error for missing sign name")
		}
	})
}

func TestNewTencentSMSProvider(t *testing.T) {
	t.Run("incomplete config returns error", func(t *testing.T) {
		cfg := TencentSMSConfig{}
		_, err := NewTencentSMSProvider(cfg)
		if err == nil {
			t.Error("Expected error for incomplete config")
		}
	})

	t.Run("missing secret ID", func(t *testing.T) {
		cfg := TencentSMSConfig{
			SecretKey:  "key",
			AppID:      "app",
			SignName:   "sign",
			TemplateID: "template",
		}
		_, err := NewTencentSMSProvider(cfg)
		if err == nil {
			t.Error("Expected error for missing secret ID")
		}
	})

	t.Run("missing app ID", func(t *testing.T) {
		cfg := TencentSMSConfig{
			SecretID:   "id",
			SecretKey:  "key",
			SignName:   "sign",
			TemplateID: "template",
		}
		_, err := NewTencentSMSProvider(cfg)
		if err == nil {
			t.Error("Expected error for missing app ID")
		}
	})
}

// =============================================================================
// SMS Code Service Tests
// =============================================================================

type mockSMSProvider struct {
	sendErr error
}

func (m *mockSMSProvider) SendVerificationCode(ctx context.Context, phone, code string) error {
	return m.sendErr
}

func TestNewSMSCodeService(t *testing.T) {
	provider := &mockSMSProvider{}
	cache := &mockCacheForSMS{}

	t.Run("with default config", func(t *testing.T) {
		cfg := SMSCodeConfig{}
		svc := NewSMSCodeService(provider, cache, cfg)
		if svc == nil {
			t.Error("Expected non-nil service")
		}
		if svc.cfg.CodeTTL <= 0 {
			t.Error("Expected default CodeTTL to be set")
		}
	})

	t.Run("with custom config", func(t *testing.T) {
		cfg := SMSCodeConfig{
			CodeTTL:        10 * time.Minute,
			ResendCooldown: 2 * time.Minute,
			MaxDailyLimit:  20,
		}
		svc := NewSMSCodeService(provider, cache, cfg)
		if svc == nil {
			t.Error("Expected non-nil service")
		}
	})
}

func TestSMSCodeService_DefaultConfig(t *testing.T) {
	cfg := DefaultSMSCodeConfig()
	if cfg.CodeTTL <= 0 {
		t.Error("Expected positive CodeTTL")
	}
	if cfg.ResendCooldown <= 0 {
		t.Error("Expected positive ResendCooldown")
	}
	if cfg.MaxDailyLimit <= 0 {
		t.Error("Expected positive MaxDailyLimit")
	}
}

// mockCacheWithGet implements cacheInterface with controllable Get behavior
type mockCacheWithGet struct {
	getResult  interface{}
	getFound   bool
	setErr     error
	deleteErr  error
	lastSetKey string
}

func (m *mockCacheWithGet) Get(ctx context.Context, key string) (interface{}, bool) {
	return m.getResult, m.getFound
}

func (m *mockCacheWithGet) Set(ctx context.Context, key string, value interface{}, l1TTL, l2TTL time.Duration) error {
	m.lastSetKey = key
	return m.setErr
}

func (m *mockCacheWithGet) Delete(ctx context.Context, key string) error {
	return m.deleteErr
}

func TestSMSCodeService_SendCode(t *testing.T) {
	provider := &mockSMSProvider{}
	cache := &mockCacheWithGet{}
	svc := NewSMSCodeService(provider, cache, SMSCodeConfig{
		CodeTTL:        5 * time.Minute,
		ResendCooldown: time.Minute,
		MaxDailyLimit:  10,
	})
	ctx := context.Background()

	t.Run("nil service returns error", func(t *testing.T) {
		var nilSvc *SMSCodeService
		_, err := nilSvc.SendCode(ctx, &SendCodeRequest{Phone: "13800138000"})
		if err == nil {
			t.Error("Expected error for nil service")
		}
	})

	t.Run("nil request returns error", func(t *testing.T) {
		_, err := svc.SendCode(ctx, nil)
		if err == nil {
			t.Error("Expected error for nil request")
		}
	})

	t.Run("invalid phone returns error", func(t *testing.T) {
		_, err := svc.SendCode(ctx, &SendCodeRequest{Phone: "invalid"})
		if err == nil {
			t.Error("Expected error for invalid phone")
		}
	})

	t.Run("cooldown active returns error", func(t *testing.T) {
		cache.getResult = true
		cache.getFound = true
		_, err := svc.SendCode(ctx, &SendCodeRequest{Phone: "13800138000"})
		if err == nil {
			t.Error("Expected error when cooldown is active")
		}
		cache.getFound = false
	})

	t.Run("successful send", func(t *testing.T) {
		cache.getResult = nil
		cache.getFound = false
		resp, err := svc.SendCode(ctx, &SendCodeRequest{Phone: "13800138000", Purpose: "login"})
		if err != nil {
			t.Fatalf("SendCode failed: %v", err)
		}
		if resp == nil {
			t.Error("Expected non-nil response")
		}
	})
}

func TestSMSCodeService_VerifyCode(t *testing.T) {
	provider := &mockSMSProvider{}
	cache := &mockCacheWithGet{}
	svc := NewSMSCodeService(provider, cache, SMSCodeConfig{
		CodeTTL:        5 * time.Minute,
		ResendCooldown: time.Minute,
		MaxDailyLimit:  10,
	})
	ctx := context.Background()

	t.Run("nil service returns error", func(t *testing.T) {
		var nilSvc *SMSCodeService
		err := nilSvc.VerifyCode(ctx, "13800138000", "login", "123456")
		if err == nil {
			t.Error("Expected error for nil service")
		}
	})

	t.Run("empty code returns error", func(t *testing.T) {
		err := svc.VerifyCode(ctx, "13800138000", "login", "")
		if err == nil {
			t.Error("Expected error for empty code")
		}
	})

	t.Run("code not found returns error", func(t *testing.T) {
		cache.getResult = nil
		cache.getFound = false
		err := svc.VerifyCode(ctx, "13800138000", "login", "123456")
		if err == nil {
			t.Error("Expected error when code not found")
		}
	})

	t.Run("wrong code returns error", func(t *testing.T) {
		cache.getResult = "654321"
		cache.getFound = true
		err := svc.VerifyCode(ctx, "13800138000", "login", "123456")
		if err == nil {
			t.Error("Expected error for wrong code")
		}
	})

	t.Run("correct code succeeds", func(t *testing.T) {
		cache.getResult = "123456"
		cache.getFound = true
		err := svc.VerifyCode(ctx, "13800138000", "login", "123456")
		if err != nil {
			t.Fatalf("VerifyCode failed: %v", err)
		}
	})
}

func TestIsValidPhone(t *testing.T) {
	tests := []struct {
		phone    string
		expected bool
	}{
		{"13800138000", true},
		{"15912345678", true},
		{"18612345678", true},
		{"12345678901", false},
		{"1380013800", false},
		{"", false},
		{"invalid", false},
	}

	for _, tt := range tests {
		result := isValidPhone(tt.phone)
		if result != tt.expected {
			t.Errorf("isValidPhone(%q) = %v, want %v", tt.phone, result, tt.expected)
		}
	}
}
