package mysql

import (
	"context"
	"fmt"
	"testing"
	"time"

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

	"wucher/internal/domain/federal_state"
	"wucher/internal/domain/icao"
	"wucher/internal/shared/pkg/uuidv7"
)

func openICAOTestDB(t *testing.T) *gorm.DB {
	t.Helper()
	dsn := fmt.Sprintf("file:icao_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)
	}
	if err := db.AutoMigrate(&icao.ICAO{}); err != nil {
		t.Fatalf("auto migrate: %v", err)
	}
	return db
}

func TestNewICAORepository(t *testing.T) {
	db := openICAOTestDB(t)
	repo := NewICAORepository(db)
	if repo == nil || repo.db == nil {
		t.Fatalf("expected repository initialized")
	}
}

func TestICAORepositoryCreate(t *testing.T) {
	db := openICAOTestDB(t)
	repo := NewICAORepository(db)
	row := &icao.ICAO{ICAOCode: "EDDB", Address: "Berlin"}

	if err := repo.Create(context.Background(), row); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(row.ID) == 0 {
		t.Fatalf("expected id set")
	}
	inactive := &icao.ICAO{ICAOCode: "EDIN", IsActive: false}
	if err := repo.Create(context.Background(), inactive); err != nil {
		t.Fatalf("create inactive: %v", err)
	}
	gotInactive, err := repo.GetByID(context.Background(), inactive.ID)
	if err != nil {
		t.Fatalf("get inactive: %v", err)
	}
	if gotInactive == nil || gotInactive.IsActive {
		t.Fatalf("expected inactive icao persisted as false")
	}

	sqlDB, _ := db.DB()
	_ = sqlDB.Close()
	if err := repo.Create(context.Background(), &icao.ICAO{ICAOCode: "EDDM"}); err == nil {
		t.Fatalf("expected error on closed DB")
	}
}

func TestICAORepositoryUpdate(t *testing.T) {
	db := openICAOTestDB(t)
	repo := NewICAORepository(db)
	row := &icao.ICAO{ICAOCode: "EDDF", Address: "Old"}
	if err := repo.Create(context.Background(), row); err != nil {
		t.Fatalf("seed create: %v", err)
	}

	row.ICAOCode = "EDDH"
	if err := repo.Update(context.Background(), row); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	loaded, err := repo.GetByID(context.Background(), row.ID)
	if err != nil || loaded == nil || loaded.ICAOCode != "EDDH" {
		t.Fatalf("expected updated row")
	}

	sqlDB, _ := db.DB()
	_ = sqlDB.Close()
	if err := repo.Update(context.Background(), row); err == nil {
		t.Fatalf("expected error on closed DB")
	}
}

func TestICAORepositoryUpdateOmitsFederalStateAssociation(t *testing.T) {
	db := openICAOTestDB(t)
	repo := NewICAORepository(db)
	row := &icao.ICAO{
		ICAOCode:       "EDDF",
		FederalStateID: uuidv7.MustBytes(),
	}
	if err := repo.Create(context.Background(), row); err != nil {
		t.Fatalf("seed create: %v", err)
	}

	newFederalStateID := uuidv7.MustBytes()
	row.FederalStateID = newFederalStateID
	row.FederalState = &federal_state.FederalState{
		ID:   uuidv7.MustBytes(),
		Name: "stale preload relation",
	}
	if err := repo.Update(context.Background(), row); err != nil {
		t.Fatalf("unexpected update error with stale relation: %v", err)
	}

	loaded, err := repo.GetByID(context.Background(), row.ID)
	if err != nil {
		t.Fatalf("reload: %v", err)
	}
	if loaded == nil {
		t.Fatalf("expected row")
	}
	if string(loaded.FederalStateID) != string(newFederalStateID) {
		t.Fatalf("expected federal_state_id updated")
	}
}

func TestICAORepositoryDelete(t *testing.T) {
	db := openICAOTestDB(t)
	repo := NewICAORepository(db)
	row := &icao.ICAO{ICAOCode: "EDDK"}
	if err := repo.Create(context.Background(), row); err != nil {
		t.Fatalf("seed create: %v", err)
	}
	deletedBy := uuidv7.MustBytes()

	if err := repo.Delete(context.Background(), row.ID, deletedBy); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	got, err := repo.GetByID(context.Background(), row.ID)
	if err != nil {
		t.Fatalf("get by id after delete: %v", err)
	}
	if got != nil {
		t.Fatalf("expected soft deleted row hidden from GetByID")
	}
}

func TestICAORepositoryGetByID(t *testing.T) {
	t.Run("found", func(t *testing.T) {
		db := openICAOTestDB(t)
		repo := NewICAORepository(db)
		row := &icao.ICAO{ICAOCode: "EDDL"}
		if err := repo.Create(context.Background(), row); err != nil {
			t.Fatalf("seed create: %v", err)
		}

		got, err := repo.GetByID(context.Background(), row.ID)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got == nil || got.ICAOCode != "EDDL" {
			t.Fatalf("expected row found")
		}
	})

	t.Run("not found", func(t *testing.T) {
		db := openICAOTestDB(t)
		repo := NewICAORepository(db)
		got, err := repo.GetByID(context.Background(), uuidv7.MustBytes())
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got != nil {
			t.Fatalf("expected nil for not found")
		}
	})

	t.Run("db error", func(t *testing.T) {
		db := openICAOTestDB(t)
		repo := NewICAORepository(db)
		sqlDB, _ := db.DB()
		_ = sqlDB.Close()

		if _, err := repo.GetByID(context.Background(), uuidv7.MustBytes()); err == nil {
			t.Fatalf("expected error on closed DB")
		}
	})
}

func TestICAORepositoryList(t *testing.T) {
	t.Run("success without limit", func(t *testing.T) {
		db := openICAOTestDB(t)
		repo := NewICAORepository(db)
		_ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "C", Address: "Gamma"})
		_ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "A", Address: "Alpha"})

		rows, total, err := repo.List(context.Background(), "", "", 0, 0)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if total != 2 || len(rows) != 2 {
			t.Fatalf("expected 2 rows, total=%d len=%d", total, len(rows))
		}
	})

	t.Run("success with filter sort and limit", func(t *testing.T) {
		db := openICAOTestDB(t)
		repo := NewICAORepository(db)
		_ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Main Base", Address: "Center"})
		_ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Backup", Address: "Secondary"})

		rows, total, err := repo.List(context.Background(), "Main", "icao_code DESC", 1, 0)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if total != 1 || len(rows) != 1 || rows[0].ICAOCode != "Main Base" {
			t.Fatalf("unexpected list result")
		}
	})

	t.Run("count error", func(t *testing.T) {
		db := openICAOTestDB(t)
		repo := NewICAORepository(db)
		if err := db.Migrator().DropTable(&icao.ICAO{}); err != nil {
			t.Fatalf("drop table: %v", err)
		}

		if _, _, err := repo.List(context.Background(), "", "", 10, 0); err == nil {
			t.Fatalf("expected count error")
		}
	})

	t.Run("find error", func(t *testing.T) {
		db := openICAOTestDB(t)
		repo := NewICAORepository(db)
		_ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Main"})

		if _, _, err := repo.List(context.Background(), "", "icao_code ASC, )", 10, 0); err == nil {
			t.Fatalf("expected find error from invalid sort")
		}
	})

	t.Run("default order active sortkey first and inactive last", func(t *testing.T) {
		db := openICAOTestDB(t)
		repo := NewICAORepository(db)
		_ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Gamma", IsActive: true})
		_ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Beta", SortKey: intPtrICAORepo(0), IsActive: true})
		_ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Charlie", SortKey: intPtrICAORepo(2), IsActive: true})
		_ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Alpha", SortKey: intPtrICAORepo(1), IsActive: true})
		_ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Zulu", IsActive: false})
		_ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Bravo", SortKey: intPtrICAORepo(9), IsActive: false})

		rows, total, err := repo.List(context.Background(), "", "", 0, 0)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if total != 6 || len(rows) != 6 {
			t.Fatalf("unexpected total/len total=%d len=%d", total, len(rows))
		}

		gotOrder := []string{rows[0].ICAOCode, rows[1].ICAOCode, rows[2].ICAOCode, rows[3].ICAOCode, rows[4].ICAOCode, rows[5].ICAOCode}
		wantOrder := []string{"Beta", "Alpha", "Charlie", "Gamma", "Bravo", "Zulu"}
		for i := range wantOrder {
			if gotOrder[i] != wantOrder[i] {
				t.Fatalf("unexpected default order: got=%v want=%v", gotOrder, wantOrder)
			}
		}
	})
}

func intPtrICAORepo(v int) *int { return &v }
