package testutil

import (
	"context"
	"log"
	"os"
	"path/filepath"
	"tercul/internal/app"
	"tercul/internal/app/analytics"
	app_auth "tercul/internal/app/auth"
	"tercul/internal/app/author"
	"tercul/internal/app/authz"
	"tercul/internal/app/book"
	"tercul/internal/app/bookmark"
	"tercul/internal/app/category"
	"tercul/internal/app/collection"
	"tercul/internal/app/comment"
	"tercul/internal/app/contribution"
	"tercul/internal/app/like"
	"tercul/internal/app/localization"
	app_search "tercul/internal/app/search"
	"tercul/internal/app/tag"
	"tercul/internal/app/translation"
	"tercul/internal/app/user"
	"tercul/internal/app/work"
	"tercul/internal/data/sql"
	"tercul/internal/domain"
	"tercul/internal/domain/search"
	"tercul/internal/jobs/linguistics"
	platform_auth "tercul/internal/platform/auth"
	platform_config "tercul/internal/platform/config"
	"time"

	"github.com/stretchr/testify/suite"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// mockSearchClient is a mock implementation of the SearchClient interface.
type mockSearchClient struct{}

func (m *mockSearchClient) IndexWork(ctx context.Context, work *domain.Work, pipeline string) error {
	return nil
}

// IntegrationTestSuite provides a comprehensive test environment with either in-memory SQLite or mock repositories
type IntegrationTestSuite struct {
	suite.Suite
	App        *app.Application
	DB         *gorm.DB
	AdminCtx   context.Context
	AdminToken string
}

// TestConfig holds configuration for the test environment
type TestConfig struct {
	UseInMemoryDB bool   // If true, use SQLite in-memory, otherwise use mock repositories
	DBPath        string // Path for SQLite file (only used if UseInMemoryDB is false)
	LogLevel      logger.LogLevel
}

// DefaultTestConfig returns a default test configuration
func DefaultTestConfig() *TestConfig {
	// Using a file-based DB is more stable for integration tests across multiple packages
	// than the in-memory one, which can behave unpredictably with `go test ./...`
	return &TestConfig{
		UseInMemoryDB: false,
		DBPath:        "test.db",
		LogLevel:      logger.Silent,
	}
}

// SetupSuite sets up the test suite with the specified configuration
func (s *IntegrationTestSuite) SetupSuite(testConfig *TestConfig) {
	if testConfig == nil {
		testConfig = DefaultTestConfig()
	}

	var dbPath string
	if !testConfig.UseInMemoryDB && testConfig.DBPath != "" {
		// Clean up previous test database file before starting
		_ = os.Remove(testConfig.DBPath)
		// Ensure directory exists
		dir := filepath.Dir(testConfig.DBPath)
		if err := os.MkdirAll(dir, 0755); err != nil {
			s.T().Fatalf("Failed to create database directory: %v", err)
		}
		dbPath = testConfig.DBPath
	} else {
		// Use in-memory database
		dbPath = ":memory:"
	}

	// Custom logger for tests
	newLogger := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags),
		logger.Config{
			SlowThreshold:             time.Second,
			LogLevel:                  testConfig.LogLevel,
			IgnoreRecordNotFoundError: true,
			Colorful:                  false,
		},
	)

	db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
		Logger: newLogger,
	})
	if err != nil {
		s.T().Fatalf("Failed to connect to test database: %v", err)
	}

	s.DB = db
	err = db.AutoMigrate(
		&domain.Work{}, &domain.User{}, &domain.Author{}, &domain.Translation{},
		&domain.Comment{}, &domain.Like{}, &domain.Bookmark{}, &domain.Collection{},
		&domain.Tag{}, &domain.Category{}, &domain.Book{}, &domain.Publisher{},
		&domain.Source{}, &domain.Copyright{}, &domain.Monetization{},
		&domain.WorkStats{}, &domain.Trending{}, &domain.UserSession{}, &domain.Localization{},
		&domain.LanguageAnalysis{}, &domain.TextMetadata{}, &domain.ReadabilityScore{},
		&domain.TranslationStats{}, &TestEntity{}, &domain.CollectionWork{}, &domain.BookWork{},
	)
	s.Require().NoError(err, "Failed to migrate database schema")

	cfg, err := platform_config.LoadConfig()
	if err != nil {
		s.T().Fatalf("Failed to load config: %v", err)
	}

	repos := sql.NewRepositories(s.DB, cfg)
	var searchClient search.SearchClient = &mockSearchClient{}
	analysisRepo := linguistics.NewGORMAnalysisRepository(s.DB)
	sentimentProvider, err := linguistics.NewGoVADERSentimentProvider()
	if err != nil {
		s.T().Fatalf("Failed to create sentiment provider: %v", err)
	}
	analyticsService := analytics.NewService(repos.Analytics, analysisRepo, repos.Translation, repos.Work, sentimentProvider)
	jwtManager := platform_auth.NewJWTManager(cfg)

	authzService := authz.NewService(repos.Work, repos.Author, repos.User, repos.Translation)
	authorService := author.NewService(repos.Author)
	bookService := book.NewService(repos.Book, authzService)
	bookmarkService := bookmark.NewService(repos.Bookmark, analyticsService)
	categoryService := category.NewService(repos.Category)
	collectionService := collection.NewService(repos.Collection)
	commentService := comment.NewService(repos.Comment, authzService, analyticsService)
	contributionCommands := contribution.NewCommands(repos.Contribution, authzService)
	contributionService := contribution.NewService(contributionCommands)
	likeService := like.NewService(repos.Like, analyticsService)
	tagService := tag.NewService(repos.Tag)
	translationService := translation.NewService(repos.Translation, authzService)
	userService := user.NewService(repos.User, authzService, repos.UserProfile)
	localizationService := localization.NewService(repos.Localization)
	authService := app_auth.NewService(repos.User, jwtManager)
	workService := work.NewService(repos.Work, repos.Author, repos.User, searchClient, authzService, analyticsService)
	searchService := app_search.NewService(searchClient, localizationService)

	s.App = app.NewApplication(
		authorService,
		bookService,
		bookmarkService,
		categoryService,
		collectionService,
		commentService,
		contributionService,
		likeService,
		tagService,
		translationService,
		userService,
		localizationService,
		authService,
		authzService,
		workService,
		searchService,
		analyticsService,
	)
}

// TearDownSuite cleans up the test suite
func (s *IntegrationTestSuite) TearDownSuite() {
	if s.DB != nil {
		sqlDB, err := s.DB.DB()
		if err == nil {
			_ = sqlDB.Close()
		}
	}
}

// SetupTest resets test data for each test
func (s *IntegrationTestSuite) SetupTest() {
	if s.DB != nil {
		// Reset database for each test
		s.DB.Exec("DELETE FROM translations")
		s.DB.Exec("DELETE FROM works")
		s.DB.Exec("DELETE FROM authors")
		s.DB.Exec("DELETE FROM users")
		s.DB.Exec("DELETE FROM books")
		s.DB.Exec("DELETE FROM bookmarks")
		s.DB.Exec("DELETE FROM collections")
		s.DB.Exec("DELETE FROM comments")
		s.DB.Exec("DELETE FROM likes")
		s.DB.Exec("DELETE FROM trendings")
		s.DB.Exec("DELETE FROM work_stats")
		s.DB.Exec("DELETE FROM translation_stats")
		s.DB.Exec("DELETE FROM collection_works")
		s.DB.Exec("DELETE FROM book_works")
	}

	// Create a default admin user for tests
	adminUser := &domain.User{
		Username: "admin",
		Email:    "admin@test.com",
		Role:     domain.UserRoleAdmin,
		Active:   true,
	}
	_ = adminUser.SetPassword("password")
	err := s.DB.Create(adminUser).Error
	s.Require().NoError(err)
	s.AdminCtx = ContextWithClaims(context.Background(), &platform_auth.Claims{
		UserID: adminUser.ID,
		Role:   string(adminUser.Role),
	})

	// Generate a token for the admin user
	cfg, err := platform_config.LoadConfig()
	s.Require().NoError(err)
	jwtManager := platform_auth.NewJWTManager(cfg)
	token, err := jwtManager.GenerateToken(adminUser)
	s.Require().NoError(err)
	s.AdminToken = token
}

// CreateTestWork creates a test work with optional content
func (s *IntegrationTestSuite) CreateTestWork(ctx context.Context, title, language string, content string) *domain.Work {
	work := &domain.Work{
		Title: title,
		TranslatableModel: domain.TranslatableModel{
			Language: language,
		},
	}
	// Note: CreateWork command might not exist or need context. Assuming it does for now.
	// If CreateWork also requires auth, this context should be s.AdminCtx
	createdWork, err := s.App.Work.Commands.CreateWork(ctx, work)
	s.Require().NoError(err)

	if content != "" {
		translationInput := translation.CreateOrUpdateTranslationInput{
			Title:              title,
			Content:            content,
			Language:           language,
			TranslatableID:     createdWork.ID,
			TranslatableType:   "works",
			IsOriginalLanguage: true, // Assuming the first one is original
		}
		_, err = s.App.Translation.Commands.CreateOrUpdateTranslation(ctx, translationInput)
		s.Require().NoError(err)
	}
	return createdWork
}

// CreateTestTranslation creates a test translation for a work.
func (s *IntegrationTestSuite) CreateTestTranslation(workID uint, language, content string) *domain.Translation {
	translationInput := translation.CreateOrUpdateTranslationInput{
		Title:            "Test Translation",
		Content:          content,
		Language:         language,
		TranslatableID:   workID,
		TranslatableType: "works",
	}
	createdTranslation, err := s.App.Translation.Commands.CreateOrUpdateTranslation(s.AdminCtx, translationInput)
	s.Require().NoError(err)
	return createdTranslation
}

// ContextWithClaims creates a new context with the given claims.
func ContextWithClaims(ctx context.Context, claims *platform_auth.Claims) context.Context {
	return context.WithValue(ctx, platform_auth.ClaimsContextKey, claims)
}
