package pso

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"testing"
	"time"

	"system-altrak/internal/domain"
	"system-altrak/internal/repository"

	"github.com/glebarez/sqlite"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

type benchmarkConfig struct {
	dbEngine    string
	dbDSN       string
	datasetSize int
}

func loadBenchmarkConfig(b *testing.B) benchmarkConfig {
	b.Helper()

	cfg := benchmarkConfig{
		dbEngine:    strings.ToLower(strings.TrimSpace(os.Getenv("PERF_DB_ENGINE"))),
		dbDSN:       strings.TrimSpace(os.Getenv("PERF_DB_DSN")),
		datasetSize: 12000,
	}

	if cfg.dbEngine == "" {
		cfg.dbEngine = "sqlite"
	}

	if raw := strings.TrimSpace(os.Getenv("PERF_DATASET_SIZE")); raw != "" {
		parsed, err := strconv.Atoi(raw)
		if err != nil || parsed <= 0 {
			b.Fatalf("invalid PERF_DATASET_SIZE=%q; expected positive integer", raw)
		}
		cfg.datasetSize = parsed
	}

	if cfg.dbEngine != "sqlite" && cfg.dbEngine != "mysql" {
		b.Fatalf("unsupported PERF_DB_ENGINE=%q; expected sqlite or mysql", cfg.dbEngine)
	}

	if cfg.dbEngine == "mysql" && cfg.dbDSN == "" {
		b.Skip("PERF_DB_DSN is required when PERF_DB_ENGINE=mysql")
	}

	return cfg
}

func openBenchmarkDB(cfg benchmarkConfig) (*gorm.DB, error) {
	gormCfg := &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}
	if cfg.dbEngine == "mysql" {
		return gorm.Open(mysql.Open(cfg.dbDSN), gormCfg)
	}

	dsn := fmt.Sprintf("file:pso_bench_%d?mode=memory&cache=shared", time.Now().UnixNano())
	return gorm.Open(sqlite.Open(dsn), gormCfg)
}

func setupPSOBenchmarkRepo(b *testing.B) PSORepository {
	b.Helper()
	cfg := loadBenchmarkConfig(b)

	db, err := openBenchmarkDB(cfg)
	if err != nil {
		b.Fatalf("failed to open benchmark db: %v", err)
	}

	if cfg.dbEngine == "mysql" {
		if err := db.Unscoped().Where("pso_no LIKE ?", "PSO-BENCH-%").Delete(&domain.OperationalRecord{}).Error; err != nil {
			b.Fatalf("failed to clean benchmark records: %v", err)
		}
	}

	if err := db.AutoMigrate(&domain.OperationalRecord{}); err != nil {
		b.Fatalf("failed to migrate benchmark schema: %v", err)
	}

	runID := time.Now().UnixNano()
	seed := make([]domain.OperationalRecord, 0, cfg.datasetSize)
	now := time.Now().UTC()
	for i := 1; i <= cfg.datasetSize; i++ {
		d := now.AddDate(0, 0, -(i % 90))
		seed = append(seed, domain.OperationalRecord{
			PsoNo:        fmt.Sprintf("PSO-BENCH-%d-%05d", runID, i),
			PoNo:         fmt.Sprintf("PO-BENCH-%d-%05d", runID, i),
			CustomerName: fmt.Sprintf("Customer %04d", i%1200),
			Amount:       int64(1000000 + (i * 10)),
			AmountIDR:    int64(1000000 + (i * 10)),
			TOP:          int64((i % 45) + 1),
			Days:         int64(i % 90),
			Discount:     float64(i%15) / 10,
			Currency:     "IDR",
			PsoDate:      &d,
			BranchID:     uint((i % 2) + 1),
			RecordStatus: "IMPORT",
		})
	}

	batchSize := 500
	if cfg.datasetSize < batchSize {
		batchSize = cfg.datasetSize
	}

	if err := db.CreateInBatches(seed, batchSize).Error; err != nil {
		b.Fatalf("failed to seed benchmark data: %v", err)
	}

	b.Logf("benchmark config: engine=%s dataset_size=%d", cfg.dbEngine, cfg.datasetSize)

	return NewRepository(repository.NewRepository(db))
}

func BenchmarkPSORepository_ListOutstandingRecords_NoFilter(b *testing.B) {
	repo := setupPSOBenchmarkRepo(b)
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		_, _, err := repo.ListOutstandingRecords(1, 100, "", 1, "", "", "")
		if err != nil {
			b.Fatalf("ListOutstandingRecords failed: %v", err)
		}
	}
}

func BenchmarkPSORepository_ListOutstandingRecords_SearchFilter(b *testing.B) {
	repo := setupPSOBenchmarkRepo(b)
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		_, _, err := repo.ListOutstandingRecords(1, 100, "PSO-00042", 1, "", "", "")
		if err != nil {
			b.Fatalf("ListOutstandingRecords with search failed: %v", err)
		}
	}
}

func BenchmarkPSORepository_ListOutstandingRecords_DateRange(b *testing.B) {
	repo := setupPSOBenchmarkRepo(b)
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		_, _, err := repo.ListOutstandingRecords(1, 100, "", 1, "2026-01-01", "2026-04-13", "")
		if err != nil {
			b.Fatalf("ListOutstandingRecords with date range failed: %v", err)
		}
	}
}

func BenchmarkPSORepository_ListOutstandingRecords_AgingFilter(b *testing.B) {
	repo := setupPSOBenchmarkRepo(b)
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		_, _, err := repo.ListOutstandingRecords(1, 100, "", 1, "", "", "30")
		if err != nil {
			b.Fatalf("ListOutstandingRecords with aging failed: %v", err)
		}
	}
}

func BenchmarkPSORepository_ProcessOutstandingRecordsBatches(b *testing.B) {
	repo := setupPSOBenchmarkRepo(b)
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		processed := 0
		err := repo.ProcessOutstandingRecordsBatches("PSO", 1, 500, func(batch []domain.OperationalRecord) error {
			processed += len(batch)
			return nil
		})
		if err != nil {
			b.Fatalf("ProcessOutstandingRecordsBatches failed: %v", err)
		}
		if processed == 0 {
			b.Fatal("expected processed records > 0")
		}
	}
}
