package main

import (
	"context"
	"errors"
	"path/filepath"
	"strings"
	"testing"

	"github.com/xuri/excelize/v2"
)

func TestParseKandangHouseTypeFile_ValidSingleRowAndDefaultHouseType(t *testing.T) {
	filePath := createWorkbook(
		t,
		"kandang_house_type",
		[]string{"kandang_id", "kandang_name", "house_type"},
		[][]string{{"101", "Kandang A1", ""}},
	)

	sheet, rows, issues, err := parseKandangHouseTypeFile(filePath, "")
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if sheet != "kandang_house_type" {
		t.Fatalf("expected sheet kandang_house_type, got %q", sheet)
	}
	if len(issues) != 0 {
		t.Fatalf("expected no issues, got %+v", issues)
	}
	if len(rows) != 1 {
		t.Fatalf("expected 1 row, got %d", len(rows))
	}
	if rows[0].KandangID != 101 {
		t.Fatalf("expected kandang_id 101, got %d", rows[0].KandangID)
	}
	if rows[0].KandangName != "Kandang A1" {
		t.Fatalf("expected kandang_name Kandang A1, got %q", rows[0].KandangName)
	}
	if rows[0].HouseType != "open_house" {
		t.Fatalf("expected default house_type open_house, got %q", rows[0].HouseType)
	}
}

func TestParseKandangHouseTypeFile_TypeHouseHeaderAlias(t *testing.T) {
	filePath := createWorkbook(
		t,
		"kandang_house_type",
		[]string{"kandang_id", "kandang_name", "type_house"},
		[][]string{{"101", "Kandang A1", "close_house"}},
	)

	_, rows, issues, err := parseKandangHouseTypeFile(filePath, "kandang_house_type")
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if len(issues) != 0 {
		t.Fatalf("expected no issues, got %+v", issues)
	}
	if len(rows) != 1 || rows[0].HouseType != "close_house" {
		t.Fatalf("expected parsed close_house row, got %+v", rows)
	}
}

func TestParseKandangHouseTypeFile_InvalidHouseType(t *testing.T) {
	filePath := createWorkbook(
		t,
		"kandang_house_type",
		[]string{"kandang_id", "kandang_name", "house_type"},
		[][]string{{"101", "Kandang A1", "semi_house"}},
	)

	_, rows, issues, err := parseKandangHouseTypeFile(filePath, "")
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if len(rows) != 0 {
		t.Fatalf("expected no valid rows, got %d", len(rows))
	}
	if !hasIssue(issues, 2, "house_type", "must be one of") {
		t.Fatalf("expected invalid house_type issue, got %+v", issues)
	}
}

func TestParseKandangHouseTypeFile_DuplicateKandangID(t *testing.T) {
	filePath := createWorkbook(
		t,
		"kandang_house_type",
		[]string{"kandang_id", "kandang_name", "house_type"},
		[][]string{
			{"101", "Kandang A1", "open_house"},
			{"101", "Kandang A2", "close_house"},
		},
	)

	_, rows, issues, err := parseKandangHouseTypeFile(filePath, "")
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if len(rows) != 1 {
		t.Fatalf("expected first row valid and second invalid, got %d", len(rows))
	}
	if !hasIssue(issues, 3, "kandang_id", "duplicate value 101") {
		t.Fatalf("expected duplicate kandang_id issue, got %+v", issues)
	}
}

func TestBuildNameMismatchIssues(t *testing.T) {
	rows := []kandangHouseTypeImportRow{{
		RowNumber:   2,
		KandangID:   10,
		KandangName: "Kandang Salah",
		HouseType:   "open_house",
	}}

	issues := buildNameMismatchIssues(rows, map[uint]string{10: "Kandang Benar"})
	if !hasIssue(issues, 2, "kandang_name", "does not match") {
		t.Fatalf("expected name mismatch issue, got %+v", issues)
	}
}

func TestApplyImportRows_Success(t *testing.T) {
	store := &fakeStore{
		changedByID:     map[uint]bool{101: true, 102: false},
		normalizeResult: 3,
	}
	runner := &fakeTransactionRunner{store: store}

	rows := []kandangHouseTypeImportRow{
		{RowNumber: 2, KandangID: 101, HouseType: "open_house"},
		{RowNumber: 3, KandangID: 102, HouseType: "close_house"},
	}

	results, normalized, err := applyImportRows(context.Background(), runner, rows)
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if runner.txCalls != 1 {
		t.Fatalf("expected 1 tx call, got %d", runner.txCalls)
	}
	if len(results) != 2 {
		t.Fatalf("expected 2 row results, got %d", len(results))
	}
	if normalized != 3 {
		t.Fatalf("expected normalized count 3, got %d", normalized)
	}
	if !results[0].Changed || results[1].Changed {
		t.Fatalf("unexpected changed flags: %+v", results)
	}
	if len(store.updateCalls) != 2 {
		t.Fatalf("expected 2 update calls, got %d", len(store.updateCalls))
	}
}

func TestApplyImportRows_FailOnUpdate(t *testing.T) {
	store := &fakeStore{
		updateErrByID: map[uint]error{101: errors.New("boom")},
	}
	runner := &fakeTransactionRunner{store: store}

	rows := []kandangHouseTypeImportRow{{RowNumber: 2, KandangID: 101, HouseType: "open_house"}}

	_, _, err := applyImportRows(context.Background(), runner, rows)
	if err == nil {
		t.Fatalf("expected error, got nil")
	}
	if !strings.Contains(err.Error(), "update failed") {
		t.Fatalf("expected update failed error, got %v", err)
	}
}

func TestCountChangedRows(t *testing.T) {
	count := countChangedRows([]applyRowResult{{Changed: true}, {Changed: false}, {Changed: true}})
	if count != 2 {
		t.Fatalf("expected 2 changed rows, got %d", count)
	}
}

type fakeTransactionRunner struct {
	store   *fakeStore
	txCalls int
}

func (f *fakeTransactionRunner) InTx(_ context.Context, fn func(store kandangHouseTypeStore) error) error {
	f.txCalls++
	return fn(f.store)
}

type updateCall struct {
	kandangID uint
	houseType string
}

type fakeStore struct {
	updateCalls     []updateCall
	changedByID     map[uint]bool
	updateErrByID   map[uint]error
	normalizeResult int64
	normalizeErr    error
}

func (f *fakeStore) UpdateKandangHouseType(_ context.Context, kandangID uint, houseType string) (bool, error) {
	f.updateCalls = append(f.updateCalls, updateCall{kandangID: kandangID, houseType: houseType})
	if err, exists := f.updateErrByID[kandangID]; exists {
		return false, err
	}
	if changed, exists := f.changedByID[kandangID]; exists {
		return changed, nil
	}
	return true, nil
}

func (f *fakeStore) NormalizeNullHouseType(_ context.Context) (int64, error) {
	if f.normalizeErr != nil {
		return 0, f.normalizeErr
	}
	return f.normalizeResult, nil
}

func createWorkbook(t *testing.T, sheetName string, headers []string, rows [][]string) string {
	t.Helper()

	f := excelize.NewFile()
	if sheetName == "" {
		sheetName = "Sheet1"
	}
	defaultSheet := f.GetSheetName(0)
	if defaultSheet != sheetName {
		idx, err := f.NewSheet(sheetName)
		if err != nil {
			t.Fatalf("failed creating sheet: %v", err)
		}
		f.SetActiveSheet(idx)
		_ = f.DeleteSheet(defaultSheet)
	}

	for idx, header := range headers {
		cell, err := excelize.CoordinatesToCellName(idx+1, 1)
		if err != nil {
			t.Fatalf("failed computing header cell: %v", err)
		}
		if err := f.SetCellValue(sheetName, cell, header); err != nil {
			t.Fatalf("failed setting header cell: %v", err)
		}
	}

	for rowIdx, row := range rows {
		for colIdx, value := range row {
			cell, err := excelize.CoordinatesToCellName(colIdx+1, rowIdx+2)
			if err != nil {
				t.Fatalf("failed computing row cell: %v", err)
			}
			if err := f.SetCellValue(sheetName, cell, value); err != nil {
				t.Fatalf("failed setting row cell: %v", err)
			}
		}
	}

	path := filepath.Join(t.TempDir(), "kandang_house_type.xlsx")
	if err := f.SaveAs(path); err != nil {
		t.Fatalf("failed saving workbook: %v", err)
	}

	return path
}

func hasIssue(issues []validationIssue, row int, field string, contains string) bool {
	for _, issue := range issues {
		if issue.Row != row {
			continue
		}
		if issue.Field != field {
			continue
		}
		if strings.Contains(issue.Message, contains) {
			return true
		}
	}
	return false
}
