package commands

import (
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"testing"
	"time"

	"tercul/internal/domain"
	"tercul/internal/platform/log"

	"github.com/blevesearch/bleve/v2"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/gorm"
)

// mockTranslationRepository is a mock implementation of TranslationRepository for testing
type mockTranslationRepository struct {
	translations []domain.Translation
	err          error
}

func (m *mockTranslationRepository) ListAll(ctx context.Context) ([]domain.Translation, error) {
	if m.err != nil {
		return nil, m.err
	}
	return m.translations, nil
}

// Implement other required methods with minimal implementations
func (m *mockTranslationRepository) GetByID(ctx context.Context, id uint) (*domain.Translation, error) {
	return nil, nil
}
func (m *mockTranslationRepository) Create(ctx context.Context, entity *domain.Translation) error {
	return nil
}
func (m *mockTranslationRepository) Update(ctx context.Context, entity *domain.Translation) error {
	return nil
}
func (m *mockTranslationRepository) Delete(ctx context.Context, id uint) error {
	return nil
}
func (m *mockTranslationRepository) List(ctx context.Context, page, pageSize int) (*domain.PaginatedResult[domain.Translation], error) {
	return nil, nil
}
func (m *mockTranslationRepository) ListByWorkID(ctx context.Context, workID uint) ([]domain.Translation, error) {
	return nil, nil
}
func (m *mockTranslationRepository) ListByWorkIDPaginated(ctx context.Context, workID uint, language *string, page, pageSize int) (*domain.PaginatedResult[domain.Translation], error) {
	return nil, nil
}
func (m *mockTranslationRepository) ListByEntity(ctx context.Context, entityType string, entityID uint) ([]domain.Translation, error) {
	return nil, nil
}
func (m *mockTranslationRepository) ListByTranslatorID(ctx context.Context, translatorID uint) ([]domain.Translation, error) {
	return nil, nil
}
func (m *mockTranslationRepository) ListByStatus(ctx context.Context, status domain.TranslationStatus) ([]domain.Translation, error) {
	return nil, nil
}
func (m *mockTranslationRepository) Upsert(ctx context.Context, translation *domain.Translation) error {
	return nil
}
func (m *mockTranslationRepository) BeginTx(ctx context.Context) (*gorm.DB, error) {
	return nil, nil
}
func (m *mockTranslationRepository) WithTx(ctx context.Context, fn func(tx *gorm.DB) error) error {
	return fn(nil)
}
func (m *mockTranslationRepository) Count(ctx context.Context) (int64, error) {
	return int64(len(m.translations)), nil
}
func (m *mockTranslationRepository) CountWithOptions(ctx context.Context, options *domain.QueryOptions) (int64, error) {
	return int64(len(m.translations)), nil
}
func (m *mockTranslationRepository) Exists(ctx context.Context, id uint) (bool, error) {
	for _, t := range m.translations {
		if t.ID == id {
			return true, nil
		}
	}
	return false, nil
}
func (m *mockTranslationRepository) GetByIDWithOptions(ctx context.Context, id uint, options *domain.QueryOptions) (*domain.Translation, error) {
	for _, t := range m.translations {
		if t.ID == id {
			return &t, nil
		}
	}
	return nil, nil
}
func (m *mockTranslationRepository) ListWithOptions(ctx context.Context, options *domain.QueryOptions) ([]domain.Translation, error) {
	return m.translations, nil
}
func (m *mockTranslationRepository) GetAllForSync(ctx context.Context, batchSize, offset int) ([]domain.Translation, error) {
	start := offset
	end := offset + batchSize
	if end > len(m.translations) {
		end = len(m.translations)
	}
	if start >= len(m.translations) {
		return []domain.Translation{}, nil
	}
	return m.translations[start:end], nil
}
func (m *mockTranslationRepository) CreateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Translation) error {
	return nil
}
func (m *mockTranslationRepository) UpdateInTx(ctx context.Context, tx *gorm.DB, entity *domain.Translation) error {
	return nil
}
func (m *mockTranslationRepository) DeleteInTx(ctx context.Context, tx *gorm.DB, id uint) error {
	return nil
}
func (m *mockTranslationRepository) FindWithPreload(ctx context.Context, preloads []string, id uint) (*domain.Translation, error) {
	return m.GetByID(ctx, id)
}

// initBleveIndexForTest creates an in-memory Bleve index for faster testing
func initBleveIndexForTest(t *testing.T) bleve.Index {
	mapping := bleve.NewIndexMapping()
	translationMapping := bleve.NewDocumentMapping()

	// Simplified mapping for tests
	idMapping := bleve.NewTextFieldMapping()
	idMapping.Store = true
	idMapping.Index = true
	idMapping.Analyzer = "keyword"
	translationMapping.AddFieldMappingsAt("id", idMapping)

	titleMapping := bleve.NewTextFieldMapping()
	titleMapping.Store = true
	titleMapping.Index = true
	titleMapping.Analyzer = "standard"
	translationMapping.AddFieldMappingsAt("title", titleMapping)

	contentMapping := bleve.NewTextFieldMapping()
	contentMapping.Store = true
	contentMapping.Index = true
	contentMapping.Analyzer = "standard"
	translationMapping.AddFieldMappingsAt("content", contentMapping)

	languageMapping := bleve.NewTextFieldMapping()
	languageMapping.Store = true
	languageMapping.Index = true
	languageMapping.Analyzer = "keyword"
	translationMapping.AddFieldMappingsAt("language", languageMapping)

	statusMapping := bleve.NewTextFieldMapping()
	statusMapping.Store = true
	statusMapping.Index = true
	statusMapping.Analyzer = "keyword"
	translationMapping.AddFieldMappingsAt("status", statusMapping)

	translatableIDMapping := bleve.NewNumericFieldMapping()
	translatableIDMapping.Store = true
	translatableIDMapping.Index = true
	translationMapping.AddFieldMappingsAt("translatable_id", translatableIDMapping)

	translatableTypeMapping := bleve.NewTextFieldMapping()
	translatableTypeMapping.Store = true
	translatableTypeMapping.Index = true
	translatableTypeMapping.Analyzer = "keyword"
	translationMapping.AddFieldMappingsAt("translatable_type", translatableTypeMapping)

	translatorIDMapping := bleve.NewNumericFieldMapping()
	translatorIDMapping.Store = true
	translatorIDMapping.Index = true
	translationMapping.AddFieldMappingsAt("translator_id", translatorIDMapping)

	mapping.AddDocumentMapping("translation", translationMapping)

	// Use in-memory index for tests
	index, err := bleve.NewMemOnly(mapping)
	require.NoError(t, err)
	return index
}

func TestInitBleveIndex(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping slow Bleve index test in short mode")
	}

	indexPath := filepath.Join(t.TempDir(), "test_index")

	// Create index first time
	index1, err := initBleveIndex(indexPath)
	require.NoError(t, err)
	require.NotNil(t, index1)

	// Close and reopen (don't use defer here since we're closing explicitly)
	err = index1.Close()
	require.NoError(t, err)

	// Try to open existing index
	index2, err := initBleveIndex(indexPath)
	assert.NoError(t, err)
	assert.NotNil(t, index2)
	if index2 != nil {
		defer func() { _ = index2.Close() }()
	}
}

func TestIndexBatch(t *testing.T) {
	index := initBleveIndexForTest(t)
	defer func() { _ = index.Close() }()

	translations := []domain.Translation{
		{
			BaseModel:        domain.BaseModel{ID: 1},
			Title:            "Test Translation 1",
			Content:          "Content 1",
			Language:         "en",
			Status:           domain.TranslationStatusPublished,
			TranslatableID:   100,
			TranslatableType: "works",
		},
		{
			BaseModel:        domain.BaseModel{ID: 2},
			Title:            "Test Translation 2",
			Content:          "Content 2",
			Language:         "fr",
			Status:           domain.TranslationStatusDraft,
			TranslatableID:   200,
			TranslatableType: "works",
		},
	}

	// Use a test logger
	err := indexBatch(index, translations)
	assert.NoError(t, err)

	// Verify documents are indexed
	doc1, err := index.Document("translation_1")
	assert.NoError(t, err)
	assert.NotNil(t, doc1)

	doc2, err := index.Document("translation_2")
	assert.NoError(t, err)
	assert.NotNil(t, doc2)
}

func TestCheckpointSaveAndLoad(t *testing.T) {
	// Use a temporary file for checkpoint
	testCheckpointFile := filepath.Join(t.TempDir(), "test_checkpoint.json")

	// Temporarily override the checkpoint file path by using a helper
	cp := &checkpoint{
		LastProcessedID: 123,
		TotalProcessed:  456,
		LastUpdated:     time.Now(),
	}

	// Save checkpoint to test file
	data, err := json.Marshal(cp)
	require.NoError(t, err)
	err = os.WriteFile(testCheckpointFile, data, 0644)
	require.NoError(t, err)

	// Load checkpoint from test file
	data, err = os.ReadFile(testCheckpointFile)
	require.NoError(t, err)
	var loaded checkpoint
	err = json.Unmarshal(data, &loaded)
	require.NoError(t, err)

	assert.Equal(t, cp.LastProcessedID, loaded.LastProcessedID)
	assert.Equal(t, cp.TotalProcessed, loaded.TotalProcessed)
}

func TestMigrateTranslations(t *testing.T) {
	index := initBleveIndexForTest(t)
	defer func() { _ = index.Close() }()

	translations := []domain.Translation{
		{
			BaseModel:        domain.BaseModel{ID: 1},
			Title:            "Test 1",
			Content:          "Content 1",
			Language:         "en",
			Status:           domain.TranslationStatusPublished,
			TranslatableID:   100,
			TranslatableType: "works",
		},
		{
			BaseModel:        domain.BaseModel{ID: 2},
			Title:            "Test 2",
			Content:          "Content 2",
			Language:         "fr",
			Status:           domain.TranslationStatusPublished,
			TranslatableID:   200,
			TranslatableType: "works",
		},
	}

	repo := &mockTranslationRepository{translations: translations}
	logger := getTestLogger()

	stats, err := migrateTranslations(
		context.Background(),
		repo,
		index,
		10,  // small batch size for testing
		nil, // no checkpoint
		logger,
	)

	assert.NoError(t, err)
	assert.NotNil(t, stats)
	assert.Equal(t, 2, stats.TotalIndexed)
	assert.Equal(t, 0, stats.TotalErrors)
}

func TestMigrateTranslationsWithCheckpoint(t *testing.T) {
	index := initBleveIndexForTest(t)
	defer func() { _ = index.Close() }()

	translations := []domain.Translation{
		{
			BaseModel:        domain.BaseModel{ID: 1},
			Title:            "Test 1",
			Content:          "Content 1",
			Language:         "en",
			Status:           domain.TranslationStatusPublished,
			TranslatableID:   100,
			TranslatableType: "works",
		},
		{
			BaseModel:        domain.BaseModel{ID: 2},
			Title:            "Test 2",
			Content:          "Content 2",
			Language:         "fr",
			Status:           domain.TranslationStatusPublished,
			TranslatableID:   200,
			TranslatableType: "works",
		},
		{
			BaseModel:        domain.BaseModel{ID: 3},
			Title:            "Test 3",
			Content:          "Content 3",
			Language:         "de",
			Status:           domain.TranslationStatusPublished,
			TranslatableID:   300,
			TranslatableType: "works",
		},
	}

	repo := &mockTranslationRepository{translations: translations}
	logger := getTestLogger()

	// Resume from checkpoint after ID 1
	cp := &checkpoint{
		LastProcessedID: 1,
		TotalProcessed:  1,
		LastUpdated:     time.Now(),
	}

	stats, err := migrateTranslations(
		context.Background(),
		repo,
		index,
		10,
		cp,
		logger,
	)

	assert.NoError(t, err)
	assert.NotNil(t, stats)
	// Should only process translations with ID > 1
	assert.Equal(t, 3, stats.TotalIndexed) // 1 from checkpoint + 2 new
}

func TestVerifyIndex(t *testing.T) {
	index := initBleveIndexForTest(t)
	defer func() { _ = index.Close() }()

	translations := []domain.Translation{
		{
			BaseModel:        domain.BaseModel{ID: 1},
			Title:            "Test 1",
			Content:          "Content 1",
			Language:         "en",
			Status:           domain.TranslationStatusPublished,
			TranslatableID:   100,
			TranslatableType: "works",
		},
	}

	repo := &mockTranslationRepository{translations: translations}
	logger := getTestLogger()

	// Index the translation first
	err := indexBatch(index, translations)
	require.NoError(t, err)

	// Verify
	err = verifyIndex(index, repo, logger, context.Background())
	assert.NoError(t, err)
}

func TestVerifyIndexWithMissingTranslation(t *testing.T) {
	index := initBleveIndexForTest(t)
	defer func() { _ = index.Close() }()

	translations := []domain.Translation{
		{
			BaseModel:        domain.BaseModel{ID: 1},
			Title:            "Test 1",
			Content:          "Content 1",
			Language:         "en",
			Status:           domain.TranslationStatusPublished,
			TranslatableID:   100,
			TranslatableType: "works",
		},
	}

	repo := &mockTranslationRepository{translations: translations}
	logger := getTestLogger()

	// Don't index - verification should fail
	err := verifyIndex(index, repo, logger, context.Background())
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "missing from index")
}

// getTestLogger creates a test logger instance
func getTestLogger() *log.Logger {
	log.Init("test", "test")
	return log.FromContext(context.Background())
}
