package service

import (
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/glebarez/sqlite"
	"github.com/go-playground/validator/v10"
	"github.com/gofiber/fiber/v2"
	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	repository "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/repositories"
	validation "gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/validations"
	"gorm.io/gorm"
)

func TestCreateOneRejectsWhenSameDateHasActiveEmptyKandang(t *testing.T) {
	svc, db := setupDailyChecklistServiceTest(t)

	insertDailyChecklistRow(t, db, 1, mustDate(t, "2026-01-10"), dailyChecklistCategoryEmptyKandang, strPtr("DRAFT"), nil)

	result, serviceErr, resp := runCreateOneRequest(t, svc, &validation.Create{
		Date:      "2026-01-10",
		KandangId: 1,
		Category:  "cleaning",
		Status:    "DRAFT",
	})

	if result != nil {
		t.Fatalf("expected nil result, got %+v", result)
	}
	assertFiberErrorCode(t, serviceErr, fiber.StatusConflict)
	if resp.StatusCode != fiber.StatusConflict {
		t.Fatalf("expected HTTP status %d, got %d", fiber.StatusConflict, resp.StatusCode)
	}
}

func TestCreateOneRejectsWhenSameDateHasRejectedEmptyKandang(t *testing.T) {
	svc, db := setupDailyChecklistServiceTest(t)

	insertDailyChecklistRow(t, db, 1, mustDate(t, "2026-01-10"), dailyChecklistCategoryEmptyKandang, strPtr(dailyChecklistStatusRejected), nil)

	result, serviceErr, resp := runCreateOneRequest(t, svc, &validation.Create{
		Date:      "2026-01-10",
		KandangId: 1,
		Category:  "cleaning",
		Status:    "DRAFT",
	})

	if result != nil {
		t.Fatalf("expected nil result, got %+v", result)
	}
	assertFiberErrorCode(t, serviceErr, fiber.StatusConflict)
	if resp.StatusCode != fiber.StatusConflict {
		t.Fatalf("expected HTTP status %d, got %d", fiber.StatusConflict, resp.StatusCode)
	}
}

func TestCreateOneAllowsWhenOnlySoftDeletedEmptyKandangExists(t *testing.T) {
	svc, db := setupDailyChecklistServiceTest(t)

	deletedAt := mustDateTime(t, "2026-01-11 10:00:00")
	insertDailyChecklistRow(t, db, 1, mustDate(t, "2026-01-10"), dailyChecklistCategoryEmptyKandang, strPtr("DRAFT"), &deletedAt)

	result, serviceErr, resp := runCreateOneRequest(t, svc, &validation.Create{
		Date:      "2026-01-10",
		KandangId: 1,
		Category:  "cleaning",
		Status:    "DRAFT",
	})

	if serviceErr != nil {
		t.Fatalf("expected no error, got %v", serviceErr)
	}
	if resp.StatusCode != fiber.StatusCreated {
		t.Fatalf("expected HTTP status %d, got %d", fiber.StatusCreated, resp.StatusCode)
	}
	if result == nil {
		t.Fatal("expected non-nil result")
	}
	if result.Category != "cleaning" {
		t.Fatalf("expected category cleaning, got %s", result.Category)
	}

	var activeCount int64
	if err := db.Model(&entity.DailyChecklist{}).
		Where("kandang_id = ? AND date = ? AND category = ? AND deleted_at IS NULL", 1, mustDate(t, "2026-01-10"), "cleaning").
		Count(&activeCount).Error; err != nil {
		t.Fatalf("failed counting active checklists: %v", err)
	}
	if activeCount != 1 {
		t.Fatalf("expected 1 active cleaning checklist, got %d", activeCount)
	}
}

func TestCreateOneRejectsBulkEmptyKandangWhenDateRangeHasConflict(t *testing.T) {
	svc, db := setupDailyChecklistServiceTest(t)

	insertDailyChecklistRow(t, db, 1, mustDate(t, "2026-01-03"), dailyChecklistCategoryEmptyKandang, strPtr("APPROVED"), nil)

	result, serviceErr, resp := runCreateOneRequest(t, svc, &validation.Create{
		Date:                "2026-01-01",
		KandangId:           1,
		Category:            "cleaning",
		Status:              "DRAFT",
		EmptyKandang:        true,
		EmptyKandangEndDate: "2026-01-05",
	})

	if result != nil {
		t.Fatalf("expected nil result, got %+v", result)
	}
	assertFiberErrorCode(t, serviceErr, fiber.StatusConflict)
	if resp.StatusCode != fiber.StatusConflict {
		t.Fatalf("expected HTTP status %d, got %d", fiber.StatusConflict, resp.StatusCode)
	}

	var activeInRange int64
	if err := db.Model(&entity.DailyChecklist{}).
		Where("kandang_id = ? AND date BETWEEN ? AND ? AND deleted_at IS NULL", 1, mustDate(t, "2026-01-01"), mustDate(t, "2026-01-05")).
		Count(&activeInRange).Error; err != nil {
		t.Fatalf("failed counting checklists in range: %v", err)
	}
	if activeInRange != 1 {
		t.Fatalf("expected only pre-existing row to remain in range, got %d rows", activeInRange)
	}
}

func TestCreateOneRejectsBulkEmptyKandangWhenRangeHasNonEmptyChecklist(t *testing.T) {
	svc, db := setupDailyChecklistServiceTest(t)

	insertDailyChecklistRow(t, db, 1, mustDate(t, "2026-01-03"), "cleaning", strPtr("APPROVED"), nil)

	result, serviceErr, resp := runCreateOneRequest(t, svc, &validation.Create{
		Date:                "2026-01-01",
		KandangId:           1,
		Category:            "cleaning",
		Status:              "DRAFT",
		EmptyKandang:        true,
		EmptyKandangEndDate: "2026-01-05",
	})

	if result != nil {
		t.Fatalf("expected nil result, got %+v", result)
	}
	assertFiberErrorCode(t, serviceErr, fiber.StatusConflict)
	if resp.StatusCode != fiber.StatusConflict {
		t.Fatalf("expected HTTP status %d, got %d", fiber.StatusConflict, resp.StatusCode)
	}
}

func TestCreateOneRejectsBulkEmptyKandangWhenRangeHasRejectedChecklist(t *testing.T) {
	svc, db := setupDailyChecklistServiceTest(t)

	insertDailyChecklistRow(t, db, 1, mustDate(t, "2026-01-03"), "cleaning", strPtr(dailyChecklistStatusRejected), nil)

	result, serviceErr, resp := runCreateOneRequest(t, svc, &validation.Create{
		Date:                "2026-01-01",
		KandangId:           1,
		Category:            "cleaning",
		Status:              "DRAFT",
		EmptyKandang:        true,
		EmptyKandangEndDate: "2026-01-05",
	})

	if result != nil {
		t.Fatalf("expected nil result, got %+v", result)
	}
	assertFiberErrorCode(t, serviceErr, fiber.StatusConflict)
	if resp.StatusCode != fiber.StatusConflict {
		t.Fatalf("expected HTTP status %d, got %d", fiber.StatusConflict, resp.StatusCode)
	}
}

func TestCreateOneAllowsBulkEmptyKandangWhenRangeHasOnlySoftDeletedChecklist(t *testing.T) {
	svc, db := setupDailyChecklistServiceTest(t)

	deletedAt := mustDateTime(t, "2026-01-11 10:00:00")
	insertDailyChecklistRow(t, db, 1, mustDate(t, "2026-01-03"), "cleaning", strPtr("APPROVED"), &deletedAt)

	result, serviceErr, resp := runCreateOneRequest(t, svc, &validation.Create{
		Date:                "2026-01-01",
		KandangId:           1,
		Category:            "cleaning",
		Status:              "DRAFT",
		EmptyKandang:        true,
		EmptyKandangEndDate: "2026-01-05",
	})

	if serviceErr != nil {
		t.Fatalf("expected no error, got %v", serviceErr)
	}
	if resp.StatusCode != fiber.StatusCreated {
		t.Fatalf("expected HTTP status %d, got %d", fiber.StatusCreated, resp.StatusCode)
	}
	if result == nil {
		t.Fatal("expected non-nil result")
	}
	if result.Category != dailyChecklistCategoryEmptyKandang {
		t.Fatalf("expected category %s, got %s", dailyChecklistCategoryEmptyKandang, result.Category)
	}

	var activeInRange int64
	if err := db.Model(&entity.DailyChecklist{}).
		Where("kandang_id = ? AND date BETWEEN ? AND ? AND deleted_at IS NULL", 1, mustDate(t, "2026-01-01"), mustDate(t, "2026-01-05")).
		Count(&activeInRange).Error; err != nil {
		t.Fatalf("failed counting checklists in range: %v", err)
	}
	if activeInRange != 5 {
		t.Fatalf("expected 5 active checklists created for range, got %d", activeInRange)
	}
}

func TestCreateOneReusesExistingChecklistWhenNoEmptyKandangConflict(t *testing.T) {
	svc, db := setupDailyChecklistServiceTest(t)

	existingID := insertDailyChecklistRow(t, db, 1, mustDate(t, "2026-01-10"), "cleaning", strPtr("APPROVED"), nil)

	result, serviceErr, resp := runCreateOneRequest(t, svc, &validation.Create{
		Date:      "2026-01-10",
		KandangId: 1,
		Category:  "cleaning",
		Status:    "DRAFT",
	})

	if serviceErr != nil {
		t.Fatalf("expected no error, got %v", serviceErr)
	}
	if resp.StatusCode != fiber.StatusCreated {
		t.Fatalf("expected HTTP status %d, got %d", fiber.StatusCreated, resp.StatusCode)
	}
	if result == nil {
		t.Fatal("expected non-nil result")
	}
	if result.Id != existingID {
		t.Fatalf("expected existing checklist id %d to be reused, got %d", existingID, result.Id)
	}

	var activeCount int64
	if err := db.Model(&entity.DailyChecklist{}).
		Where("kandang_id = ? AND date = ? AND category = ? AND deleted_at IS NULL", 1, mustDate(t, "2026-01-10"), "cleaning").
		Count(&activeCount).Error; err != nil {
		t.Fatalf("failed counting active checklists: %v", err)
	}
	if activeCount != 1 {
		t.Fatalf("expected 1 active cleaning checklist, got %d", activeCount)
	}
}

func setupDailyChecklistServiceTest(t *testing.T) (DailyChecklistService, *gorm.DB) {
	t.Helper()

	db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=private"), &gorm.Config{})
	if err != nil {
		t.Fatalf("failed opening sqlite db: %v", err)
	}

	statements := []string{
		`CREATE TABLE areas (
			id INTEGER PRIMARY KEY,
			name TEXT NOT NULL,
			created_by INTEGER NOT NULL,
			created_at DATETIME NULL,
			updated_at DATETIME NULL,
			deleted_at DATETIME NULL
		)`,
		`CREATE TABLE locations (
			id INTEGER PRIMARY KEY,
			name TEXT NOT NULL,
			address TEXT NOT NULL,
			area_id INTEGER NOT NULL,
			created_by INTEGER NOT NULL,
			created_at DATETIME NULL,
			updated_at DATETIME NULL,
			deleted_at DATETIME NULL
		)`,
		`CREATE TABLE kandang_groups (
			id INTEGER PRIMARY KEY,
			name TEXT NOT NULL,
			status TEXT NOT NULL,
			location_id INTEGER NOT NULL,
			pic_id INTEGER NOT NULL,
			created_by INTEGER NOT NULL,
			created_at DATETIME NULL,
			updated_at DATETIME NULL,
			deleted_at DATETIME NULL
		)`,
		`CREATE TABLE daily_checklists (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			kandang_id INTEGER NOT NULL,
			checklist_id INTEGER NULL,
			date DATE NOT NULL,
			name TEXT NULL,
			status TEXT NULL,
			category TEXT NOT NULL,
			total_score INTEGER NULL,
			document_path TEXT NULL,
			reject_reason TEXT NULL,
			created_by INTEGER NULL,
			deleted_by INTEGER NULL,
			created_at DATETIME NULL,
			updated_at DATETIME NULL,
			deleted_at DATETIME NULL
		)`,
		`INSERT INTO areas (id, name, created_by, created_at, updated_at, deleted_at) VALUES (1, 'Area A', 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL)`,
		`INSERT INTO locations (id, name, address, area_id, created_by, created_at, updated_at, deleted_at) VALUES (1, 'Farm A', 'Address', 1, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL)`,
		`INSERT INTO kandang_groups (id, name, status, location_id, pic_id, created_by, created_at, updated_at, deleted_at) VALUES (1, 'Kandang A', 'ACTIVE', 1, 1, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL)`,
	}

	for _, stmt := range statements {
		if err := db.Exec(stmt).Error; err != nil {
			t.Fatalf("failed preparing schema: %v", err)
		}
	}

	repo := repository.NewDailyChecklistRepository(db)
	svc := NewDailyChecklistService(repo, nil, validator.New(), nil)
	return svc, db
}

func runCreateOneRequest(t *testing.T, svc DailyChecklistService, req *validation.Create) (*entity.DailyChecklist, error, *http.Response) {
	t.Helper()

	app := fiber.New()
	var (
		result     *entity.DailyChecklist
		serviceErr error
	)

	app.Post("/", func(c *fiber.Ctx) error {
		result, serviceErr = svc.CreateOne(c, req)
		if serviceErr != nil {
			return serviceErr
		}
		return c.SendStatus(fiber.StatusCreated)
	})

	resp, err := app.Test(httptest.NewRequest(http.MethodPost, "/", nil))
	if err != nil {
		t.Fatalf("failed running fiber request: %v", err)
	}

	return result, serviceErr, resp
}

func insertDailyChecklistRow(t *testing.T, db *gorm.DB, kandangID uint, date time.Time, category string, status *string, deletedAt *time.Time) uint {
	t.Helper()

	row := &entity.DailyChecklist{
		KandangId: kandangID,
		Date:      date,
		Category:  category,
		Status:    status,
	}
	if deletedAt != nil {
		row.DeletedAt = gorm.DeletedAt{
			Time:  *deletedAt,
			Valid: true,
		}
	}

	if err := db.Create(row).Error; err != nil {
		t.Fatalf("failed inserting daily checklist row: %v", err)
	}
	return row.Id
}

func assertFiberErrorCode(t *testing.T, err error, expectedCode int) {
	t.Helper()

	if err == nil {
		t.Fatal("expected error, got nil")
	}

	var fiberErr *fiber.Error
	if !errors.As(err, &fiberErr) {
		t.Fatalf("expected *fiber.Error, got %T (%v)", err, err)
	}
	if fiberErr.Code != expectedCode {
		t.Fatalf("expected fiber error code %d, got %d", expectedCode, fiberErr.Code)
	}
}

func mustDate(t *testing.T, raw string) time.Time {
	t.Helper()

	value, err := time.Parse(dailyChecklistDateLayout, raw)
	if err != nil {
		t.Fatalf("failed parsing date %q: %v", raw, err)
	}
	return value
}

func mustDateTime(t *testing.T, raw string) time.Time {
	t.Helper()

	value, err := time.Parse("2006-01-02 15:04:05", raw)
	if err != nil {
		t.Fatalf("failed parsing datetime %q: %v", raw, err)
	}
	return value
}

func strPtr(value string) *string {
	v := value
	return &v
}
