package mysql

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

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

	"wucher/internal/domain/base"
	filemanager "wucher/internal/domain/file_manager"
	"wucher/internal/service"
	"wucher/internal/shared/pkg/uuidv7"
)

func openBaseTestDB(t *testing.T) *gorm.DB {
	t.Helper()
	dsn := fmt.Sprintf("file:base_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(&base.BaseCategory{}, &filemanager.Folder{}, &filemanager.File{}, &filemanager.Attachment{}, &base.Base{}, &base.BaseContactRole{}); err != nil {
		t.Fatalf("auto migrate: %v", err)
	}
	if err := db.AutoMigrate(&base.BaseOperationalShiftTime{}); err != nil {
		t.Fatalf("auto migrate base operational shift time: %v", err)
	}
	return db
}

func TestBaseRepositoryUpdateBase_ReplacesFotoAttachmentID(t *testing.T) {
	db := openBaseTestDB(t)
	repo := NewBaseRepository(db)
	now := time.Now().UTC()

	category := &base.BaseCategory{
		ID:        uuidv7.MustBytes(),
		Key:       base.CategoryKeyRegular,
		Name:      "Regular",
		CreatedAt: now,
		UpdatedAt: now,
	}
	if err := db.Create(category).Error; err != nil {
		t.Fatalf("create category: %v", err)
	}

	folder := &filemanager.Folder{
		ID:             uuidv7.MustBytes(),
		Name:           "photos",
		NameNormalized: "photos",
		Depth:          0,
		PathCache:      "/photos",
		NameSlot:       "live",
		CreatedAt:      now,
		UpdatedAt:      now,
	}
	if err := db.Create(folder).Error; err != nil {
		t.Fatalf("create folder: %v", err)
	}

	makeFile := func(name, key string) *filemanager.File {
		return &filemanager.File{
			ID:             uuidv7.MustBytes(),
			FolderID:       folder.ID,
			Name:           name,
			NameNormalized: strings.ToLower(name),
			Extension:      "webp",
			SizeBytes:      1234,
			MimeType:       "image/webp",
			Bucket:         "bucket",
			ObjectKey:      key,
			Status:         filemanager.FileStatusReady,
			NameSlot:       "live",
			CreatedAt:      now,
			UpdatedAt:      now,
		}
	}
	oldFile := makeFile("old.webp", "objects/old.webp")
	newFile := makeFile("new.webp", "objects/new.webp")
	if err := db.Create(oldFile).Error; err != nil {
		t.Fatalf("create old file: %v", err)
	}
	if err := db.Create(newFile).Error; err != nil {
		t.Fatalf("create new file: %v", err)
	}

	oldAttachment := &filemanager.Attachment{
		ID:        uuidv7.MustBytes(),
		FileID:    oldFile.ID,
		RefType:   "base_photo",
		RefID:     "base-1",
		IsPrimary: true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	newAttachment := &filemanager.Attachment{
		ID:        uuidv7.MustBytes(),
		FileID:    newFile.ID,
		RefType:   "base_photo",
		RefID:     "base-1",
		IsPrimary: true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	if err := db.Create(oldAttachment).Error; err != nil {
		t.Fatalf("create old attachment: %v", err)
	}
	if err := db.Create(newAttachment).Error; err != nil {
		t.Fatalf("create new attachment: %v", err)
	}

	row := &base.Base{
		ID:               uuidv7.MustBytes(),
		BaseCategoryID:   category.ID,
		BaseName:         "Base Old",
		FotoAttachmentID: oldAttachment.ID,
		FotoAttachment:   oldAttachment,
		CreatedAt:        now,
		UpdatedAt:        now,
	}
	if err := db.Create(row).Error; err != nil {
		t.Fatalf("seed create base: %v", err)
	}

	row.FotoAttachmentID = newAttachment.ID
	row.FotoAttachment = oldAttachment
	if err := repo.UpdateBase(context.Background(), row, base.CategoryKeyRegular); err != nil {
		t.Fatalf("update base: %v", err)
	}

	loaded, err := repo.GetBaseByID(context.Background(), row.ID, base.CategoryKeyRegular)
	if err != nil {
		t.Fatalf("get by id after update: %v", err)
	}
	if loaded == nil {
		t.Fatalf("expected updated row")
	}
	if string(loaded.FotoAttachmentID) != string(newAttachment.ID) {
		t.Fatalf("expected foto_attachment_id replaced")
	}
}

func TestBaseRepositoryListBases_DefaultOrderActiveSortkey(t *testing.T) {
	db := openBaseTestDB(t)
	repo := NewBaseRepository(db)
	now := time.Now().UTC()

	category := &base.BaseCategory{
		ID:        uuidv7.MustBytes(),
		Key:       base.CategoryKeyRegular,
		Name:      "Regular",
		CreatedAt: now,
		UpdatedAt: now,
	}
	if err := db.Create(category).Error; err != nil {
		t.Fatalf("create category: %v", err)
	}

	seed := []base.Base{
		{ID: uuidv7.MustBytes(), BaseName: "Gamma", IsActive: true},
		{ID: uuidv7.MustBytes(), BaseName: "Beta", SortKey: intPtrBaseRepo(0), IsActive: true},
		{ID: uuidv7.MustBytes(), BaseName: "Charlie", SortKey: intPtrBaseRepo(2), IsActive: true},
		{ID: uuidv7.MustBytes(), BaseName: "Alpha", SortKey: intPtrBaseRepo(1), IsActive: true},
		{ID: uuidv7.MustBytes(), BaseName: "Zulu", IsActive: false},
		{ID: uuidv7.MustBytes(), BaseName: "Bravo", SortKey: intPtrBaseRepo(9), IsActive: false},
	}
	for i := range seed {
		if err := repo.CreateBase(context.Background(), &seed[i], base.CategoryKeyRegular); err != nil {
			t.Fatalf("seed create: %v", err)
		}
	}

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

	gotOrder := []string{rows[0].BaseName, rows[1].BaseName, rows[2].BaseName, rows[3].BaseName, rows[4].BaseName, rows[5].BaseName}
	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 TestBaseRepositoryCreateAndGetOperationalShiftTimes(t *testing.T) {
	db := openBaseTestDB(t)
	repo := NewBaseRepository(db)
	now := time.Now().UTC()

	category := &base.BaseCategory{
		ID:        uuidv7.MustBytes(),
		Key:       base.CategoryKeyRegular,
		Name:      "Regular",
		CreatedAt: now,
		UpdatedAt: now,
	}
	if err := db.Create(category).Error; err != nil {
		t.Fatalf("create category: %v", err)
	}
	start := time.Date(1900, 1, 1, 9, 0, 0, 0, time.UTC)
	end := time.Date(1900, 1, 1, 18, 0, 0, 0, time.UTC)
	date := time.Date(2026, 4, 12, 0, 0, 0, 0, time.UTC)
	row := &base.Base{
		ID:             uuidv7.MustBytes(),
		BaseCategoryID: category.ID,
		BaseName:       "Base Ops",
		OperationalShiftTimes: []base.BaseOperationalShiftTime{
			{
				DateStart:     &date,
				DateEnd:       &date,
				StartTimeType: base.ShiftTimeTypeFixed,
				EndTimeType:   base.ShiftTimeTypeFixed,
				ShiftStart:    start.Format("15:04:05"),
				ShiftEnd:      end.Format("15:04:05"),
			},
		},
		CreatedAt: now,
		UpdatedAt: now,
	}
	if err := repo.CreateBase(context.Background(), row, base.CategoryKeyRegular); err != nil {
		t.Fatalf("create base: %v", err)
	}
	loaded, err := repo.GetBaseByID(context.Background(), row.ID, base.CategoryKeyRegular)
	if err != nil {
		t.Fatalf("get base: %v", err)
	}
	if loaded == nil || len(loaded.OperationalShiftTimes) != 1 {
		t.Fatalf("expected operational shift time to preload")
	}
	if got := loaded.OperationalShiftTimes[0].DateStart.Format("2006-01-02"); got != "2026-04-12" {
		t.Fatalf("unexpected date start: %s", got)
	}
	if got := loaded.OperationalShiftTimes[0].DateEnd.Format("2006-01-02"); got != "2026-04-12" {
		t.Fatalf("unexpected date end: %s", got)
	}
	if got := loaded.OperationalShiftTimes[0].ShiftStart; got != "09:00:00" {
		t.Fatalf("unexpected shift start: %#v", got)
	}
	if got := loaded.OperationalShiftTimes[0].StartTimeType; got != base.ShiftTimeTypeFixed {
		t.Fatalf("unexpected start_time_type: %#v", got)
	}
}

func TestBaseRepositoryCreateOperationalShiftTimes_AllowsNilShiftForNonFixedTypes(t *testing.T) {
	db := openBaseTestDB(t)
	repo := NewBaseRepository(db)
	now := time.Now().UTC()

	category := &base.BaseCategory{
		ID:        uuidv7.MustBytes(),
		Key:       base.CategoryKeyRegular,
		Name:      "Regular",
		CreatedAt: now,
		UpdatedAt: now,
	}
	if err := db.Create(category).Error; err != nil {
		t.Fatalf("create category: %v", err)
	}

	date := time.Date(2026, 4, 12, 0, 0, 0, 0, time.UTC)
	row := &base.Base{
		ID:             uuidv7.MustBytes(),
		BaseCategoryID: category.ID,
		BaseName:       "Base Ops Empty Shift",
		OperationalShiftTimes: []base.BaseOperationalShiftTime{
			{
				DateStart:     &date,
				DateEnd:       &date,
				StartTimeType: base.ShiftTimeTypeBMCT,
				EndTimeType:   base.ShiftTimeTypeECET,
			},
		},
		CreatedAt: now,
		UpdatedAt: now,
	}
	if err := repo.CreateBase(context.Background(), row, base.CategoryKeyRegular); err != nil {
		t.Fatalf("create base: %v", err)
	}

	loaded, err := repo.GetBaseByID(context.Background(), row.ID, base.CategoryKeyRegular)
	if err != nil {
		t.Fatalf("get base: %v", err)
	}
	if loaded == nil || len(loaded.OperationalShiftTimes) != 1 {
		t.Fatalf("expected operational shift time to preload")
	}
	if got := loaded.OperationalShiftTimes[0].StartTimeType; got != base.ShiftTimeTypeBMCT {
		t.Fatalf("unexpected start_time_type: %#v", got)
	}
	if got := loaded.OperationalShiftTimes[0].EndTimeType; got != base.ShiftTimeTypeECET {
		t.Fatalf("unexpected end_time_type: %#v", got)
	}
	if got := loaded.OperationalShiftTimes[0].ShiftStart; got != "" {
		t.Fatalf("expected empty shift start for non-fixed type, got %#v", got)
	}
	if got := loaded.OperationalShiftTimes[0].ShiftEnd; got != "" {
		t.Fatalf("expected empty shift end for non-fixed type, got %#v", got)
	}
}

func TestBaseServiceCreateDetailed_PersistsFixedOperationalShiftTimes(t *testing.T) {
	db := openBaseTestDB(t)
	repo := NewBaseRepository(db)
	svc := service.NewBaseService(repo)
	now := time.Now().UTC()

	category := &base.BaseCategory{
		ID:        uuidv7.MustBytes(),
		Key:       base.CategoryKeyRegular,
		Name:      "Regular",
		CreatedAt: now,
		UpdatedAt: now,
	}
	if err := db.Create(category).Error; err != nil {
		t.Fatalf("create category: %v", err)
	}

	start := time.Date(2000, 1, 1, 3, 0, 0, 0, time.UTC)
	end := time.Date(2000, 1, 1, 5, 0, 0, 0, time.UTC)
	date := time.Date(2026, 6, 15, 0, 0, 0, 0, time.UTC)
	res, err := svc.CreateDetailed(context.Background(), service.BaseCreateInput{
		ID:                   uuidv7.MustBytes(),
		CategoryType:         base.CategoryKeyRegular,
		BaseName:             "Base Ops Fixed",
		Latitude:             47.3769,
		Longitude:            8.5417,
		DefaultStartTimeType: base.ShiftTimeTypeFixed,
		DefaultEndTimeType:   base.ShiftTimeTypeFixed,
		DefaultShiftStart:    &start,
		DefaultShiftEnd:      &end,
		OperationalShiftTimes: []service.BaseOperationalShiftTimeInput{
			{
				DateStart:     date,
				DateEnd:       date,
				StartTimeType: base.ShiftTimeTypeFixed,
				EndTimeType:   base.ShiftTimeTypeFixed,
				ShiftStart:    &start,
				ShiftEnd:      &end,
			},
		},
	})
	if err != nil {
		t.Fatalf("create detailed: %v", err)
	}
	if res == nil || res.Row == nil {
		t.Fatalf("expected create detailed result row")
	}

	loaded, err := repo.GetBaseByID(context.Background(), res.Row.ID, base.CategoryKeyRegular)
	if err != nil {
		t.Fatalf("get base: %v", err)
	}
	if loaded == nil || len(loaded.OperationalShiftTimes) != 1 {
		t.Fatalf("expected operational shift time to preload")
	}
	if got := loaded.OperationalShiftTimes[0].ShiftStart; got != "03:00:00" {
		t.Fatalf("unexpected shift start stored: %#v", got)
	}
	if got := loaded.OperationalShiftTimes[0].ShiftEnd; got != "05:00:00" {
		t.Fatalf("unexpected shift end stored: %#v", got)
	}
}

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