package depot_test

import (
	"context"
	"os"
	"testing"
	"time"

	"github.com/flowy-live/llink/internal/depot"
	"github.com/flowy-live/llink/internal/testhelper"
	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/stretchr/testify/assert"
)

var dbPool *pgxpool.Pool

func TestMain(m *testing.M) {
	dbPool = testhelper.SetupTestDB()
	defer testhelper.TeardownTestDB()

	ret := m.Run()
	os.Exit(ret)
}

// TestDepotRepository tests the repository layer directly
// These tests can run without GCS since they only test database operations
func TestDepotRepository_CreateAndGet(t *testing.T) {
	ctx := context.Background()

	// Create a depot object directly in the database for testing
	var id string
	err := dbPool.QueryRow(ctx,
		`INSERT INTO depot_objects (id, name, content_type, content_length, bucket_name, object_key, contains_content)
		 VALUES ($1, $2, $3, $4, $5, $6, $7)
		 RETURNING id`,
		"dpo_test123", "test-file.txt", "text/plain", int64(1024), "test-bucket", "test-key-123", false,
	).Scan(&id)
	assert.NoError(t, err)
	assert.Equal(t, "dpo_test123", id)

	// Query the object back
	var obj struct {
		ID              string
		Name            string
		ContentType     string
		ContentLength   int64
		BucketName      string
		ObjectKey       string
		ContainsContent bool
		CreatedAt       time.Time
	}
	err = dbPool.QueryRow(ctx,
		`SELECT id, name, content_type, content_length, bucket_name, object_key, contains_content, created_at
		 FROM depot_objects WHERE id = $1`,
		id,
	).Scan(&obj.ID, &obj.Name, &obj.ContentType, &obj.ContentLength,
		&obj.BucketName, &obj.ObjectKey, &obj.ContainsContent, &obj.CreatedAt)
	assert.NoError(t, err)
	assert.Equal(t, "test-file.txt", obj.Name)
	assert.Equal(t, "text/plain", obj.ContentType)
	assert.Equal(t, int64(1024), obj.ContentLength)
	assert.Equal(t, "test-bucket", obj.BucketName)
	assert.Equal(t, "test-key-123", obj.ObjectKey)
	assert.False(t, obj.ContainsContent)
	assert.False(t, obj.CreatedAt.IsZero())

	// Clean up
	_, err = dbPool.Exec(ctx, `DELETE FROM depot_objects WHERE id = $1`, id)
	assert.NoError(t, err)
}

func TestDepotRepository_ConfirmUpload(t *testing.T) {
	ctx := context.Background()

	// Create a depot object
	var id string
	err := dbPool.QueryRow(ctx,
		`INSERT INTO depot_objects (id, name, content_type, content_length, bucket_name, object_key, contains_content)
		 VALUES ($1, $2, $3, $4, $5, $6, $7)
		 RETURNING id`,
		"dpo_confirm123", "confirm-file.txt", "text/plain", int64(2048), "test-bucket", "confirm-key", false,
	).Scan(&id)
	assert.NoError(t, err)

	// Verify it starts with contains_content = false
	var containsContent bool
	err = dbPool.QueryRow(ctx,
		`SELECT contains_content FROM depot_objects WHERE id = $1`,
		id,
	).Scan(&containsContent)
	assert.NoError(t, err)
	assert.False(t, containsContent)

	// Confirm upload
	_, err = dbPool.Exec(ctx,
		`UPDATE depot_objects SET contains_content = TRUE WHERE id = $1`,
		id,
	)
	assert.NoError(t, err)

	// Verify it's now true
	err = dbPool.QueryRow(ctx,
		`SELECT contains_content FROM depot_objects WHERE id = $1`,
		id,
	).Scan(&containsContent)
	assert.NoError(t, err)
	assert.True(t, containsContent)

	// Clean up
	_, err = dbPool.Exec(ctx, `DELETE FROM depot_objects WHERE id = $1`, id)
	assert.NoError(t, err)
}

func TestDepotRepository_Delete(t *testing.T) {
	ctx := context.Background()

	// Create a depot object
	var id string
	err := dbPool.QueryRow(ctx,
		`INSERT INTO depot_objects (id, name, content_type, content_length, bucket_name, object_key, contains_content)
		 VALUES ($1, $2, $3, $4, $5, $6, $7)
		 RETURNING id`,
		"dpo_delete123", "delete-file.txt", "text/plain", int64(512), "test-bucket", "delete-key", false,
	).Scan(&id)
	assert.NoError(t, err)

	// Delete it
	result, err := dbPool.Exec(ctx, `DELETE FROM depot_objects WHERE id = $1`, id)
	assert.NoError(t, err)
	assert.Equal(t, int64(1), result.RowsAffected())

	// Verify it's gone
	var count int
	err = dbPool.QueryRow(ctx,
		`SELECT COUNT(*) FROM depot_objects WHERE id = $1`,
		id,
	).Scan(&count)
	assert.NoError(t, err)
	assert.Equal(t, 0, count)
}

func TestDepotRepository_Exists(t *testing.T) {
	ctx := context.Background()

	// Create a depot object
	var id string
	err := dbPool.QueryRow(ctx,
		`INSERT INTO depot_objects (id, name, content_type, content_length, bucket_name, object_key, contains_content)
		 VALUES ($1, $2, $3, $4, $5, $6, $7)
		 RETURNING id`,
		"dpo_exists123", "exists-file.txt", "text/plain", int64(256), "test-bucket", "exists-key", true,
	).Scan(&id)
	assert.NoError(t, err)

	// Check exists
	var exists bool
	err = dbPool.QueryRow(ctx,
		`SELECT EXISTS(SELECT 1 FROM depot_objects WHERE id = $1)`,
		id,
	).Scan(&exists)
	assert.NoError(t, err)
	assert.True(t, exists)

	// Check non-existent
	err = dbPool.QueryRow(ctx,
		`SELECT EXISTS(SELECT 1 FROM depot_objects WHERE id = $1)`,
		"dpo_nonexistent",
	).Scan(&exists)
	assert.NoError(t, err)
	assert.False(t, exists)

	// Clean up
	_, err = dbPool.Exec(ctx, `DELETE FROM depot_objects WHERE id = $1`, id)
	assert.NoError(t, err)
}

func TestDepotRepository_OrphanedIndex(t *testing.T) {
	ctx := context.Background()

	// Create an orphaned object (contains_content = false)
	var orphanID string
	err := dbPool.QueryRow(ctx,
		`INSERT INTO depot_objects (id, name, content_type, content_length, bucket_name, object_key, contains_content)
		 VALUES ($1, $2, $3, $4, $5, $6, $7)
		 RETURNING id`,
		"dpo_orphan123", "orphan-file.txt", "text/plain", int64(128), "test-bucket", "orphan-key", false,
	).Scan(&orphanID)
	assert.NoError(t, err)

	// Create a confirmed object (contains_content = true)
	var confirmedID string
	err = dbPool.QueryRow(ctx,
		`INSERT INTO depot_objects (id, name, content_type, content_length, bucket_name, object_key, contains_content)
		 VALUES ($1, $2, $3, $4, $5, $6, $7)
		 RETURNING id`,
		"dpo_confirmed123", "confirmed-file.txt", "text/plain", int64(128), "test-bucket", "confirmed-key", true,
	).Scan(&confirmedID)
	assert.NoError(t, err)

	// Query orphaned objects using the index
	rows, err := dbPool.Query(ctx,
		`SELECT id FROM depot_objects WHERE contains_content = FALSE`)
	assert.NoError(t, err)
	defer rows.Close()

	var orphanedIDs []string
	for rows.Next() {
		var id string
		err := rows.Scan(&id)
		assert.NoError(t, err)
		orphanedIDs = append(orphanedIDs, id)
	}

	// Our orphan should be in the list
	assert.Contains(t, orphanedIDs, orphanID)
	assert.NotContains(t, orphanedIDs, confirmedID)

	// Clean up
	_, err = dbPool.Exec(ctx, `DELETE FROM depot_objects WHERE id IN ($1, $2)`, orphanID, confirmedID)
	assert.NoError(t, err)
}

// TestDepotModels tests the model structures
func TestDepotModels(t *testing.T) {
	// Test Config defaults
	config := depot.Config{
		BucketName: "test-bucket",
	}
	assert.Equal(t, "test-bucket", config.BucketName)
	assert.Equal(t, time.Duration(0), config.UploadURLExpiry)
	assert.Equal(t, time.Duration(0), config.DownloadURLExpiry)

	// Test with explicit values
	config = depot.Config{
		BucketName:        "custom-bucket",
		UploadURLExpiry:   10 * time.Minute,
		DownloadURLExpiry: 12 * time.Hour,
	}
	assert.Equal(t, "custom-bucket", config.BucketName)
	assert.Equal(t, 10*time.Minute, config.UploadURLExpiry)
	assert.Equal(t, 12*time.Hour, config.DownloadURLExpiry)
}

// TestDepotErrors tests the error definitions
func TestDepotErrors(t *testing.T) {
	assert.Error(t, depot.ErrNotFound)
	assert.Error(t, depot.ErrInvalidInput)
	assert.Equal(t, "object not found", depot.ErrNotFound.Error())
	assert.Equal(t, "invalid input", depot.ErrInvalidInput.Error())
}
