//go:build unit

package service

import (
	"context"
	"testing"

	"github.com/Wei-Shaw/sub2api/internal/config"
	"github.com/stretchr/testify/require"
)

// ==================== RefreshClient ====================

func TestRefreshClient(t *testing.T) {
	s := newS3StorageWithCDN("https://cdn.example.com")
	require.NotNil(t, s.client)
	require.NotNil(t, s.cfg)

	s.RefreshClient()
	require.Nil(t, s.client)
	require.Nil(t, s.cfg)
}

func TestRefreshClient_AlreadyNil(t *testing.T) {
	s := NewSoraS3Storage(nil)
	s.RefreshClient() // 不应 panic
	require.Nil(t, s.client)
	require.Nil(t, s.cfg)
}

// ==================== GetMediaTypeFromKey ====================

func TestGetMediaTypeFromKey_VideoExtensions(t *testing.T) {
	for _, ext := range []string{".mp4", ".mov", ".webm", ".m4v", ".avi", ".mkv", ".3gp", ".flv"} {
		require.Equal(t, "video", GetMediaTypeFromKey("path/to/file"+ext), "ext=%s", ext)
	}
}

func TestGetMediaTypeFromKey_VideoUpperCase(t *testing.T) {
	require.Equal(t, "video", GetMediaTypeFromKey("file.MP4"))
	require.Equal(t, "video", GetMediaTypeFromKey("file.MOV"))
}

func TestGetMediaTypeFromKey_ImageExtensions(t *testing.T) {
	require.Equal(t, "image", GetMediaTypeFromKey("file.png"))
	require.Equal(t, "image", GetMediaTypeFromKey("file.jpg"))
	require.Equal(t, "image", GetMediaTypeFromKey("file.jpeg"))
	require.Equal(t, "image", GetMediaTypeFromKey("file.gif"))
	require.Equal(t, "image", GetMediaTypeFromKey("file.webp"))
}

func TestGetMediaTypeFromKey_NoExtension(t *testing.T) {
	require.Equal(t, "image", GetMediaTypeFromKey("file"))
	require.Equal(t, "image", GetMediaTypeFromKey("path/to/file"))
}

func TestGetMediaTypeFromKey_UnknownExtension(t *testing.T) {
	require.Equal(t, "image", GetMediaTypeFromKey("file.bin"))
	require.Equal(t, "image", GetMediaTypeFromKey("file.xyz"))
}

// ==================== Enabled ====================

func TestEnabled_NilSettingService(t *testing.T) {
	s := NewSoraS3Storage(nil)
	require.False(t, s.Enabled(context.Background()))
}

func TestEnabled_ConfigDisabled(t *testing.T) {
	settingRepo := newStubSettingRepoForQuota(map[string]string{
		SettingKeySoraS3Enabled: "false",
		SettingKeySoraS3Bucket:  "test-bucket",
	})
	settingService := NewSettingService(settingRepo, &config.Config{})
	s := NewSoraS3Storage(settingService)
	require.False(t, s.Enabled(context.Background()))
}

func TestEnabled_ConfigEnabledWithBucket(t *testing.T) {
	settingRepo := newStubSettingRepoForQuota(map[string]string{
		SettingKeySoraS3Enabled: "true",
		SettingKeySoraS3Bucket:  "my-bucket",
	})
	settingService := NewSettingService(settingRepo, &config.Config{})
	s := NewSoraS3Storage(settingService)
	require.True(t, s.Enabled(context.Background()))
}

func TestEnabled_ConfigEnabledEmptyBucket(t *testing.T) {
	settingRepo := newStubSettingRepoForQuota(map[string]string{
		SettingKeySoraS3Enabled: "true",
	})
	settingService := NewSettingService(settingRepo, &config.Config{})
	s := NewSoraS3Storage(settingService)
	require.False(t, s.Enabled(context.Background()))
}

// ==================== initClient ====================

func TestInitClient_Disabled(t *testing.T) {
	settingRepo := newStubSettingRepoForQuota(map[string]string{
		SettingKeySoraS3Enabled: "false",
	})
	settingService := NewSettingService(settingRepo, &config.Config{})
	s := NewSoraS3Storage(settingService)

	_, _, err := s.getClient(context.Background())
	require.Error(t, err)
	require.Contains(t, err.Error(), "disabled")
}

func TestInitClient_IncompleteConfig(t *testing.T) {
	settingRepo := newStubSettingRepoForQuota(map[string]string{
		SettingKeySoraS3Enabled: "true",
		SettingKeySoraS3Bucket:  "test-bucket",
		// 缺少 access_key_id 和 secret_access_key
	})
	settingService := NewSettingService(settingRepo, &config.Config{})
	s := NewSoraS3Storage(settingService)

	_, _, err := s.getClient(context.Background())
	require.Error(t, err)
	require.Contains(t, err.Error(), "incomplete")
}

func TestInitClient_DefaultRegion(t *testing.T) {
	settingRepo := newStubSettingRepoForQuota(map[string]string{
		SettingKeySoraS3Enabled:         "true",
		SettingKeySoraS3Bucket:          "test-bucket",
		SettingKeySoraS3AccessKeyID:     "AKID",
		SettingKeySoraS3SecretAccessKey: "SECRET",
		// Region 为空 → 默认 us-east-1
	})
	settingService := NewSettingService(settingRepo, &config.Config{})
	s := NewSoraS3Storage(settingService)

	client, cfg, err := s.getClient(context.Background())
	require.NoError(t, err)
	require.NotNil(t, client)
	require.Equal(t, "test-bucket", cfg.Bucket)
}

func TestInitClient_DoubleCheck(t *testing.T) {
	// 验证双重检查锁定：第二次 getClient 命中缓存
	settingRepo := newStubSettingRepoForQuota(map[string]string{
		SettingKeySoraS3Enabled:         "true",
		SettingKeySoraS3Bucket:          "test-bucket",
		SettingKeySoraS3AccessKeyID:     "AKID",
		SettingKeySoraS3SecretAccessKey: "SECRET",
	})
	settingService := NewSettingService(settingRepo, &config.Config{})
	s := NewSoraS3Storage(settingService)

	client1, _, err1 := s.getClient(context.Background())
	require.NoError(t, err1)
	client2, _, err2 := s.getClient(context.Background())
	require.NoError(t, err2)
	require.Equal(t, client1, client2) // 同一客户端实例
}

func TestInitClient_NilSettingService(t *testing.T) {
	s := NewSoraS3Storage(nil)
	_, _, err := s.getClient(context.Background())
	require.Error(t, err)
	require.Contains(t, err.Error(), "setting service not available")
}

// ==================== GenerateObjectKey ====================

func TestGenerateObjectKey_ExtWithoutDot(t *testing.T) {
	s := NewSoraS3Storage(nil)
	key := s.GenerateObjectKey("", 1, "mp4")
	require.Contains(t, key, ".mp4")
	require.True(t, len(key) > 0)
}

func TestGenerateObjectKey_ExtWithDot(t *testing.T) {
	s := NewSoraS3Storage(nil)
	key := s.GenerateObjectKey("", 1, ".mp4")
	require.Contains(t, key, ".mp4")
	// 不应出现 ..mp4
	require.NotContains(t, key, "..mp4")
}

func TestGenerateObjectKey_WithPrefix(t *testing.T) {
	s := NewSoraS3Storage(nil)
	key := s.GenerateObjectKey("uploads/", 42, ".png")
	require.True(t, len(key) > 0)
	require.Contains(t, key, "uploads/sora/42/")
}

func TestGenerateObjectKey_PrefixWithoutTrailingSlash(t *testing.T) {
	s := NewSoraS3Storage(nil)
	key := s.GenerateObjectKey("uploads", 42, ".png")
	require.Contains(t, key, "uploads/sora/42/")
}

// ==================== GeneratePresignedURL ====================

func TestGeneratePresignedURL_GetClientError(t *testing.T) {
	s := NewSoraS3Storage(nil) // settingService=nil → getClient 失败
	_, err := s.GeneratePresignedURL(context.Background(), "key", 3600)
	require.Error(t, err)
}

// ==================== GetAccessURL ====================

func TestGetAccessURL_CDN(t *testing.T) {
	s := newS3StorageWithCDN("https://cdn.example.com")
	url, err := s.GetAccessURL(context.Background(), "sora/1/2024/01/01/video.mp4")
	require.NoError(t, err)
	require.Equal(t, "https://cdn.example.com/sora/1/2024/01/01/video.mp4", url)
}

func TestGetAccessURL_CDNTrailingSlash(t *testing.T) {
	s := newS3StorageWithCDN("https://cdn.example.com/")
	url, err := s.GetAccessURL(context.Background(), "key.mp4")
	require.NoError(t, err)
	require.Equal(t, "https://cdn.example.com/key.mp4", url)
}

func TestGetAccessURL_GetClientError(t *testing.T) {
	s := NewSoraS3Storage(nil)
	_, err := s.GetAccessURL(context.Background(), "key")
	require.Error(t, err)
}

// ==================== TestConnection ====================

func TestTestConnection_GetClientError(t *testing.T) {
	s := NewSoraS3Storage(nil)
	err := s.TestConnection(context.Background())
	require.Error(t, err)
}

// ==================== UploadFromURL ====================

func TestUploadFromURL_GetClientError(t *testing.T) {
	s := NewSoraS3Storage(nil)
	_, _, err := s.UploadFromURL(context.Background(), 1, "https://example.com/file.mp4")
	require.Error(t, err)
}

// ==================== DeleteObjects ====================

func TestDeleteObjects_EmptyKeys(t *testing.T) {
	s := NewSoraS3Storage(nil)
	err := s.DeleteObjects(context.Background(), []string{})
	require.NoError(t, err) // 空列表直接返回
}

func TestDeleteObjects_NilKeys(t *testing.T) {
	s := NewSoraS3Storage(nil)
	err := s.DeleteObjects(context.Background(), nil)
	require.NoError(t, err) // nil 列表直接返回
}

func TestDeleteObjects_GetClientError(t *testing.T) {
	s := NewSoraS3Storage(nil)
	err := s.DeleteObjects(context.Background(), []string{"key1", "key2"})
	require.Error(t, err)
}
