package mysql

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"gorm.io/driver/sqlite"
	"gorm.io/gorm"

	"wucher/internal/config"
	"wucher/internal/domain/auth"
	"wucher/internal/domain/base"
	facility "wucher/internal/domain/facility"
	filemanager "wucher/internal/domain/file_manager"
	flightdata "wucher/internal/domain/flight_data"
	"wucher/internal/domain/helicopter"
	"wucher/internal/domain/mission"
	"wucher/internal/domain/transient"
)

func findMySQLDSNForTest() (string, bool) {
	if v := strings.TrimSpace(os.Getenv("MYSQL_DSN")); v != "" {
		return v, true
	}

	dir, err := os.Getwd()
	if err != nil {
		return "", false
	}
	for i := 0; i < 8; i++ {
		envPath := filepath.Join(dir, ".env")
		raw, err := os.ReadFile(envPath)
		if err == nil {
			lines := strings.Split(string(raw), "\n")
			for _, line := range lines {
				line = strings.TrimSpace(line)
				if line == "" || strings.HasPrefix(line, "#") {
					continue
				}
				if strings.HasPrefix(line, "MYSQL_DSN=") {
					v := strings.TrimSpace(strings.TrimPrefix(line, "MYSQL_DSN="))
					v = strings.Trim(v, `"'`)
					if v != "" {
						return v, true
					}
				}
			}
		}

		parent := filepath.Dir(dir)
		if parent == dir {
			break
		}
		dir = parent
	}
	return "", false
}

func openDBTestSQLite(t *testing.T) *gorm.DB {
	t.Helper()
	dsn := fmt.Sprintf("file:db_test_%d?mode=memory&cache=shared", time.Now().UnixNano())
	db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{
		NowFunc: func() time.Time { return time.Now().UTC() },
	})
	if err != nil {
		t.Fatalf("open sqlite: %v", err)
	}
	return db
}

func TestConnect(t *testing.T) {
	t.Run("missing dsn", func(t *testing.T) {
		if _, err := Connect(config.MySQLConfig{}); err == nil {
			t.Fatalf("expected missing dsn error")
		}
	})

	t.Run("invalid dsn", func(t *testing.T) {
		_, err := Connect(config.MySQLConfig{
			DSN: "root:pass@tcp(127.0.0.1:1)/wucher?loc=%",
		})
		if err == nil {
			t.Fatalf("expected connect error for invalid DSN")
		}
	})

	t.Run("success", func(t *testing.T) {
		dsn, ok := findMySQLDSNForTest()
		if !ok {
			t.Skip("MYSQL_DSN not found in env or .env")
		}

		cfg := config.MySQLConfig{
			DSN:             dsn,
			MaxOpenConns:    7,
			MaxIdleConns:    5,
			ConnMaxLifetime: 2 * time.Minute,
			ConnMaxIdleTime: time.Minute,
		}
		db, err := Connect(cfg)
		if err != nil {
			t.Skipf("skip connect success branch (mysql unavailable): %v", err)
		}
		sqlDB, err := db.DB()
		if err != nil {
			t.Fatalf("db.DB error: %v", err)
		}
		defer sqlDB.Close()

		if err := sqlDB.PingContext(context.Background()); err != nil {
			t.Fatalf("ping mysql: %v", err)
		}
		if got := sqlDB.Stats().MaxOpenConnections; got != cfg.MaxOpenConns {
			t.Fatalf("unexpected max open conns, got=%d want=%d", got, cfg.MaxOpenConns)
		}
	})
}

func TestAutoMigrate(t *testing.T) {
	t.Run("success and base category backfill", func(t *testing.T) {
		db := openDBTestSQLite(t)

		if err := db.Exec(`CREATE TABLE helicopters (
			id BLOB PRIMARY KEY,
			designation TEXT,
			identifier TEXT,
			report_sequence INTEGER DEFAULT 0,
			type TEXT,
			counter INTEGER DEFAULT 0,
			maintenance TEXT
		)`).Error; err != nil {
			t.Fatalf("create legacy helicopters table: %v", err)
		}
		if err := db.Exec(`CREATE TABLE bases (
			id BLOB PRIMARY KEY,
			base TEXT
		)`).Error; err != nil {
			t.Fatalf("create legacy bases table: %v", err)
		}
		if err := db.Exec(`INSERT INTO bases(id, base) VALUES (X'00112233445566778899aabbccddeeff', 'Legacy Base')`).Error; err != nil {
			t.Fatalf("seed legacy bases row: %v", err)
		}
		if err := AutoMigrate(db); err != nil {
			t.Fatalf("auto migrate: %v", err)
		}
		if db.Migrator().HasColumn(&helicopter.Helicopter{}, "maintenance") {
			t.Fatalf("expected deprecated helicopters.maintenance dropped")
		}
		if db.Migrator().HasColumn(&helicopter.Helicopter{}, "counter") {
			t.Fatalf("expected deprecated helicopters.counter dropped")
		}
		if !db.Migrator().HasColumn(&helicopter.Helicopter{}, "foto_attachment_id") {
			t.Fatalf("expected helicopters.foto_attachment_id migrated")
		}
		if !db.Migrator().HasTable(&base.BaseCategory{}) {
			t.Fatalf("expected base_categories table migrated")
		}
		if !db.Migrator().HasColumn(&base.Base{}, "base_category_id") {
			t.Fatalf("expected bases.base_category_id migrated")
		}
		if !db.Migrator().HasColumn(&base.Base{}, "latitude") {
			t.Fatalf("expected bases.latitude migrated")
		}
		if !db.Migrator().HasColumn(&base.Base{}, "longitude") {
			t.Fatalf("expected bases.longitude migrated")
		}
		if !db.Migrator().HasColumn(&base.Base{}, "default_shift_start") {
			t.Fatalf("expected bases.default_shift_start migrated")
		}
		if !db.Migrator().HasColumn(&base.Base{}, "default_shift_end") {
			t.Fatalf("expected bases.default_shift_end migrated")
		}
		if !db.Migrator().HasColumn(&base.Base{}, "foto_attachment_id") {
			t.Fatalf("expected bases.foto_attachment_id migrated")
		}
		if !db.Migrator().HasColumn(&base.Base{}, "deleted_at") {
			t.Fatalf("expected bases.deleted_at migrated")
		}
		if !db.Migrator().HasColumn(&base.Base{}, "deleted_by") {
			t.Fatalf("expected bases.deleted_by migrated")
		}
		if !db.Migrator().HasTable(&base.BaseOperationalShiftTime{}) {
			t.Fatalf("expected base_operational_shift_times table migrated")
		}
		if !db.Migrator().HasColumn(&base.BaseOperationalShiftTime{}, "base_id") {
			t.Fatalf("expected base_operational_shift_times.base_id migrated")
		}
		if !db.Migrator().HasColumn(&base.BaseOperationalShiftTime{}, "date_start") {
			t.Fatalf("expected base_operational_shift_times.date_start migrated")
		}
		if !db.Migrator().HasColumn(&base.BaseOperationalShiftTime{}, "date_end") {
			t.Fatalf("expected base_operational_shift_times.date_end migrated")
		}
		if !db.Migrator().HasColumn(&base.BaseOperationalShiftTime{}, "start_time_type") {
			t.Fatalf("expected base_operational_shift_times.start_time_type migrated")
		}
		if !db.Migrator().HasColumn(&base.BaseOperationalShiftTime{}, "end_time_type") {
			t.Fatalf("expected base_operational_shift_times.end_time_type migrated")
		}
		if !db.Migrator().HasColumn(&mission.Mission{}, "start_time") {
			t.Fatalf("expected missions.start_time migrated")
		}
		if !db.Migrator().HasColumn(&mission.Mission{}, "end_time") {
			t.Fatalf("expected missions.end_time migrated")
		}
		var missingCategory int64
		if err := db.Table("bases").
			Where("base_category_id IS NULL OR LENGTH(base_category_id) = 0").
			Count(&missingCategory).Error; err != nil {
			t.Fatalf("count missing base category: %v", err)
		}
		if missingCategory != 0 {
			t.Fatalf("expected all bases to have category, missing=%d", missingCategory)
		}
		var seededCategories int64
		if err := db.Table("base_categories").
			Where("`key` IN (?, ?)", base.CategoryKeyRegular, base.CategoryKeyHEMS).
			Count(&seededCategories).Error; err != nil {
			t.Fatalf("count seeded categories: %v", err)
		}
		if seededCategories != 2 {
			t.Fatalf("expected regular+hems seeded categories, got=%d", seededCategories)
		}
		if !db.Migrator().HasTable(&auth.User{}) {
			t.Fatalf("expected core tables migrated")
		}
		if !db.Migrator().HasTable(&auth.UserRole{}) {
			t.Fatalf("expected user_roles table migrated")
		}
		if !db.Migrator().HasTable(&transient.TokenEntry{}) || !db.Migrator().HasTable(&transient.QueueIdempotencyRecord{}) {
			t.Fatalf("expected transient runtime tables migrated")
		}
		if !db.Migrator().HasTable(&filemanager.Folder{}) || !db.Migrator().HasTable(&filemanager.File{}) {
			t.Fatalf("expected file manager tables migrated")
		}
		if !db.Migrator().HasColumn(&auth.Permission{}, "requires_pin") {
			t.Fatalf("expected permissions.requires_pin migrated")
		}
	})

	t.Run("facility category backfill", func(t *testing.T) {
		db := openDBTestSQLite(t)
		if err := db.AutoMigrate(&facility.Facility{}); err != nil {
			t.Fatalf("auto migrate facilities: %v", err)
		}
		for _, category := range []string{
			"fuel-truck",
			"heslo-sling",
			"logging-sling",
			"hec-sling",
			"hems-hec-sling",
		} {
			if err := db.Create(&facility.Facility{Category: category, Name: category, Type: "Sling"}).Error; err != nil {
				t.Fatalf("seed facility category %q: %v", category, err)
			}
		}
		if err := backfillFacilityCategories(db); err != nil {
			t.Fatalf("backfillFacilityCategories: %v", err)
		}
		expected := []string{
			"heslo-rope-label",
			"heslo-rope-length",
			"heslo-hook",
			"hec-rope-label",
			"hec-rope-length",
		}
		for _, category := range expected {
			var count int64
			if err := db.Table("facilities").Where("category = ?", category).Count(&count).Error; err != nil {
				t.Fatalf("count category %q: %v", category, err)
			}
			if count != 1 {
				t.Fatalf("expected one row for category %q, got %d", category, count)
			}
		}
	})

	t.Run("auto migrate error when db closed", func(t *testing.T) {
		db := openDBTestSQLite(t)
		sqlDB, err := db.DB()
		if err != nil {
			t.Fatalf("db.DB: %v", err)
		}
		_ = sqlDB.Close()

		if err := AutoMigrate(db); err == nil {
			t.Fatalf("expected auto migrate error on closed DB")
		}
	})

	t.Run("drop deprecated maintenance from legacy composite key schema", func(t *testing.T) {
		db := openDBTestSQLite(t)

		// Keep maintenance as part of PK to force drop-column failure on SQLite.
		if err := db.Exec(`CREATE TABLE helicopters (
			id BLOB NOT NULL,
			maintenance TEXT NOT NULL,
			designation TEXT,
			identifier TEXT,
			type TEXT,
			report_sequence INTEGER DEFAULT 0,
			PRIMARY KEY (id, maintenance)
		)`).Error; err != nil {
			t.Fatalf("create legacy helicopters table: %v", err)
		}

		if err := AutoMigrate(db); err != nil {
			t.Fatalf("unexpected auto migrate error: %v", err)
		}
	})

	t.Run("ensure flight data status column on legacy table", func(t *testing.T) {
		db := openDBTestSQLite(t)
		if err := db.Exec(`CREATE TABLE flight_data (
			id BLOB PRIMARY KEY
		)`).Error; err != nil {
			t.Fatalf("create legacy flight_data table: %v", err)
		}
		if err := ensureFlightDataStatusColumn(db); err != nil {
			t.Fatalf("ensure status column: %v", err)
		}
		if !db.Migrator().HasColumn(&flightdata.FlightData{}, "status") {
			t.Fatal("expected flight_data.status column to exist")
		}
	})

}
