package engine

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

// MockBackupEngine implements BackupEngine for testing
type MockBackupEngine struct {
	name              string
	description       string
	available         bool
	availReason       string
	supportsRestore   bool
	supportsIncr      bool
	supportsStreaming bool
	backupResult      *BackupResult
	backupError       error
	restoreError      error
}

func (m *MockBackupEngine) Name() string        { return m.name }
func (m *MockBackupEngine) Description() string { return m.description }

func (m *MockBackupEngine) CheckAvailability(ctx context.Context) (*AvailabilityResult, error) {
	return &AvailabilityResult{
		Available: m.available,
		Reason:    m.availReason,
	}, nil
}

func (m *MockBackupEngine) Backup(ctx context.Context, opts *BackupOptions) (*BackupResult, error) {
	if m.backupError != nil {
		return nil, m.backupError
	}
	if m.backupResult != nil {
		return m.backupResult, nil
	}
	return &BackupResult{
		Engine:    m.name,
		StartTime: time.Now().Add(-time.Minute),
		EndTime:   time.Now(),
		TotalSize: 1024 * 1024,
	}, nil
}

func (m *MockBackupEngine) Restore(ctx context.Context, opts *RestoreOptions) error {
	return m.restoreError
}

func (m *MockBackupEngine) SupportsRestore() bool     { return m.supportsRestore }
func (m *MockBackupEngine) SupportsIncremental() bool { return m.supportsIncr }
func (m *MockBackupEngine) SupportsStreaming() bool   { return m.supportsStreaming }

// MockStreamingEngine implements StreamingEngine
type MockStreamingEngine struct {
	MockBackupEngine
	backupToWriterResult *BackupResult
	backupToWriterError  error
}

func (m *MockStreamingEngine) BackupToWriter(ctx context.Context, w io.Writer, opts *BackupOptions) (*BackupResult, error) {
	if m.backupToWriterError != nil {
		return nil, m.backupToWriterError
	}
	if m.backupToWriterResult != nil {
		return m.backupToWriterResult, nil
	}
	// Write some test data
	w.Write([]byte("test backup data"))
	return &BackupResult{
		Engine:    m.name,
		StartTime: time.Now().Add(-time.Minute),
		EndTime:   time.Now(),
		TotalSize: 16,
	}, nil
}

func TestRegistryRegisterAndGet(t *testing.T) {
	registry := NewRegistry()

	engine := &MockBackupEngine{
		name:        "test-engine",
		description: "Test backup engine",
		available:   true,
	}

	registry.Register(engine)

	got, err := registry.Get("test-engine")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got == nil {
		t.Fatal("expected to get registered engine")
	}
	if got.Name() != "test-engine" {
		t.Errorf("expected name 'test-engine', got %s", got.Name())
	}
}

func TestRegistryGetNonExistent(t *testing.T) {
	registry := NewRegistry()

	_, err := registry.Get("nonexistent")
	if err == nil {
		t.Error("expected error for non-existent engine")
	}
}

func TestRegistryList(t *testing.T) {
	registry := NewRegistry()

	engine1 := &MockBackupEngine{name: "engine1"}
	engine2 := &MockBackupEngine{name: "engine2"}

	registry.Register(engine1)
	registry.Register(engine2)

	list := registry.List()
	if len(list) != 2 {
		t.Errorf("expected 2 engines, got %d", len(list))
	}
}

func TestRegistryRegisterDuplicate(t *testing.T) {
	registry := NewRegistry()

	engine1 := &MockBackupEngine{name: "test", description: "first"}
	engine2 := &MockBackupEngine{name: "test", description: "second"}

	registry.Register(engine1)
	registry.Register(engine2) // Should replace

	got, _ := registry.Get("test")
	if got.Description() != "second" {
		t.Error("duplicate registration should replace existing engine")
	}
}

func TestBackupResult(t *testing.T) {
	result := &BackupResult{
		Engine:       "test",
		StartTime:    time.Now().Add(-time.Minute),
		EndTime:      time.Now(),
		TotalSize:    1024 * 1024 * 100, // 100 MB
		BinlogFile:   "mysql-bin.000001",
		BinlogPos:    12345,
		GTIDExecuted: "uuid:1-100",
		Files: []BackupFile{
			{
				Path:     "/backup/backup.tar.gz",
				Size:     1024 * 1024 * 100,
				Checksum: "sha256:abc123",
			},
		},
	}

	if result.Engine != "test" {
		t.Errorf("expected engine 'test', got %s", result.Engine)
	}

	if len(result.Files) != 1 {
		t.Errorf("expected 1 file, got %d", len(result.Files))
	}
}

func TestProgress(t *testing.T) {
	progress := Progress{
		Stage:      "copying",
		Percent:    50.0,
		BytesDone:  512 * 1024 * 1024,
		BytesTotal: 1024 * 1024 * 1024,
	}

	if progress.Stage != "copying" {
		t.Errorf("expected stage 'copying', got %s", progress.Stage)
	}

	if progress.Percent != 50.0 {
		t.Errorf("expected percent 50.0, got %f", progress.Percent)
	}
}

func TestAvailabilityResult(t *testing.T) {
	tests := []struct {
		name   string
		result AvailabilityResult
	}{
		{
			name: "available",
			result: AvailabilityResult{
				Available: true,
				Info:      map[string]string{"version": "8.0.30"},
			},
		},
		{
			name: "not available",
			result: AvailabilityResult{
				Available: false,
				Reason:    "MySQL 8.0.17+ required for clone plugin",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if !tt.result.Available && tt.result.Reason == "" {
				t.Error("unavailable result should have a reason")
			}
		})
	}
}

func TestRecoveryTarget(t *testing.T) {
	now := time.Now()

	tests := []struct {
		name   string
		target RecoveryTarget
	}{
		{
			name: "time target",
			target: RecoveryTarget{
				Type: "time",
				Time: now,
			},
		},
		{
			name: "gtid target",
			target: RecoveryTarget{
				Type: "gtid",
				GTID: "uuid:1-100",
			},
		},
		{
			name: "position target",
			target: RecoveryTarget{
				Type: "position",
				File: "mysql-bin.000001",
				Pos:  12345,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.target.Type == "" {
				t.Error("target type should be set")
			}
		})
	}
}

func TestMockEngineBackup(t *testing.T) {
	engine := &MockBackupEngine{
		name:      "mock",
		available: true,
		backupResult: &BackupResult{
			Engine:     "mock",
			TotalSize:  1024,
			BinlogFile: "test",
			BinlogPos:  123,
		},
	}

	ctx := context.Background()
	opts := &BackupOptions{
		OutputDir: "/test",
	}

	result, err := engine.Backup(ctx, opts)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if result.Engine != "mock" {
		t.Errorf("expected engine 'mock', got %s", result.Engine)
	}

	if result.BinlogFile != "test" {
		t.Errorf("expected binlog file 'test', got %s", result.BinlogFile)
	}
}

func TestMockStreamingEngine(t *testing.T) {
	engine := &MockStreamingEngine{
		MockBackupEngine: MockBackupEngine{
			name:              "mock-streaming",
			supportsStreaming: true,
		},
	}

	if !engine.SupportsStreaming() {
		t.Error("expected streaming support")
	}

	ctx := context.Background()
	var buf mockWriter
	opts := &BackupOptions{}

	result, err := engine.BackupToWriter(ctx, &buf, opts)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if result.Engine != "mock-streaming" {
		t.Errorf("expected engine 'mock-streaming', got %s", result.Engine)
	}

	if len(buf.data) == 0 {
		t.Error("expected data to be written")
	}
}

type mockWriter struct {
	data []byte
}

func (m *mockWriter) Write(p []byte) (int, error) {
	m.data = append(m.data, p...)
	return len(p), nil
}

func TestDefaultRegistry(t *testing.T) {
	// DefaultRegistry should be initialized
	if DefaultRegistry == nil {
		t.Error("DefaultRegistry should not be nil")
	}
}

// Benchmark tests
func BenchmarkRegistryGet(b *testing.B) {
	registry := NewRegistry()
	for i := 0; i < 10; i++ {
		registry.Register(&MockBackupEngine{
			name: string(rune('a' + i)),
		})
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		registry.Get("e")
	}
}

func BenchmarkRegistryList(b *testing.B) {
	registry := NewRegistry()
	for i := 0; i < 10; i++ {
		registry.Register(&MockBackupEngine{
			name: string(rune('a' + i)),
		})
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		registry.List()
	}
}
