package log

import (
	"fmt"
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

func TestHasRefundCommissionLog(t *testing.T) {
	const orderNo = "ORD-REFUND-1"

	t.Run("returns true when 333 log exists for the order", func(t *testing.T) {
		db, mock, cleanup := newRefundLogTestDB(t)
		defer cleanup()

		mock.ExpectQuery("FROM `system_logs`").
			WithArgs(uint8(33), fmt.Sprintf(`%%"order_no":"%s"%%`, orderNo)).
			WillReturnRows(sqlmock.NewRows([]string{"id", "content"}).
				AddRow(1, fmt.Sprintf(`{"type":331,"order_no":"%s","amount":100}`, orderNo)).
				AddRow(2, fmt.Sprintf(`{"type":333,"order_no":"%s","amount":-100}`, orderNo)))

		got, err := HasRefundCommissionLog(db, orderNo)
		if err != nil {
			t.Fatalf("HasRefundCommissionLog error: %v", err)
		}
		if !got {
			t.Fatalf("HasRefundCommissionLog = false, want true")
		}
		assertRefundLogExpectations(t, mock)
	})

	t.Run("returns false when only 331/332 logs exist", func(t *testing.T) {
		db, mock, cleanup := newRefundLogTestDB(t)
		defer cleanup()

		mock.ExpectQuery("FROM `system_logs`").
			WithArgs(uint8(33), fmt.Sprintf(`%%"order_no":"%s"%%`, orderNo)).
			WillReturnRows(sqlmock.NewRows([]string{"id", "content"}).
				AddRow(1, fmt.Sprintf(`{"type":331,"order_no":"%s","amount":100}`, orderNo)).
				AddRow(2, fmt.Sprintf(`{"type":332,"order_no":"%s","amount":50}`, orderNo)))

		got, err := HasRefundCommissionLog(db, orderNo)
		if err != nil {
			t.Fatalf("HasRefundCommissionLog error: %v", err)
		}
		if got {
			t.Fatalf("HasRefundCommissionLog = true, want false")
		}
		assertRefundLogExpectations(t, mock)
	})

	t.Run("returns false when no log exists for the order", func(t *testing.T) {
		db, mock, cleanup := newRefundLogTestDB(t)
		defer cleanup()

		mock.ExpectQuery("FROM `system_logs`").
			WithArgs(uint8(33), fmt.Sprintf(`%%"order_no":"%s"%%`, orderNo)).
			WillReturnRows(sqlmock.NewRows([]string{"id", "content"}))

		got, err := HasRefundCommissionLog(db, orderNo)
		if err != nil {
			t.Fatalf("HasRefundCommissionLog error: %v", err)
		}
		if got {
			t.Fatalf("HasRefundCommissionLog = true, want false")
		}
		assertRefundLogExpectations(t, mock)
	})

	t.Run("returns false for empty order_no without querying", func(t *testing.T) {
		db, mock, cleanup := newRefundLogTestDB(t)
		defer cleanup()

		got, err := HasRefundCommissionLog(db, "")
		if err != nil {
			t.Fatalf("HasRefundCommissionLog error: %v", err)
		}
		if got {
			t.Fatalf("HasRefundCommissionLog = true, want false")
		}
		assertRefundLogExpectations(t, mock)
	})

	t.Run("ignores 333 log when order_no in content does not match", func(t *testing.T) {
		// Defensive: LIKE pattern may match a substring; JSON match catches it.
		db, mock, cleanup := newRefundLogTestDB(t)
		defer cleanup()

		mock.ExpectQuery("FROM `system_logs`").
			WithArgs(uint8(33), fmt.Sprintf(`%%"order_no":"%s"%%`, orderNo)).
			WillReturnRows(sqlmock.NewRows([]string{"id", "content"}).
				AddRow(1, `{"type":333,"order_no":"OTHER","amount":-100}`))

		got, err := HasRefundCommissionLog(db, orderNo)
		if err != nil {
			t.Fatalf("HasRefundCommissionLog error: %v", err)
		}
		if got {
			t.Fatalf("HasRefundCommissionLog = true, want false (different order_no)")
		}
		assertRefundLogExpectations(t, mock)
	})

	t.Run("ignores malformed json content", func(t *testing.T) {
		db, mock, cleanup := newRefundLogTestDB(t)
		defer cleanup()

		mock.ExpectQuery("FROM `system_logs`").
			WithArgs(uint8(33), fmt.Sprintf(`%%"order_no":"%s"%%`, orderNo)).
			WillReturnRows(sqlmock.NewRows([]string{"id", "content"}).
				AddRow(1, `not a json`).
				AddRow(2, fmt.Sprintf(`{"type":333,"order_no":"%s","amount":-100}`, orderNo)))

		got, err := HasRefundCommissionLog(db, orderNo)
		if err != nil {
			t.Fatalf("HasRefundCommissionLog error: %v", err)
		}
		if !got {
			t.Fatalf("HasRefundCommissionLog = false, want true")
		}
		assertRefundLogExpectations(t, mock)
	})

	t.Run("returns error when db query fails", func(t *testing.T) {
		db, mock, cleanup := newRefundLogTestDB(t)
		defer cleanup()

		mock.ExpectQuery("FROM `system_logs`").
			WithArgs(uint8(33), fmt.Sprintf(`%%"order_no":"%s"%%`, orderNo)).
			WillReturnError(fmt.Errorf("connection lost"))

		if _, err := HasRefundCommissionLog(db, orderNo); err == nil {
			t.Fatalf("expected error, got nil")
		}
		assertRefundLogExpectations(t, mock)
	})
}

func TestHasOrderRefundLog(t *testing.T) {
	const orderNo = "ORD-REFUND-AUDIT-1"

	t.Run("returns true when order refund audit log exists", func(t *testing.T) {
		db, mock, cleanup := newRefundLogTestDB(t)
		defer cleanup()

		mock.ExpectQuery("FROM `system_logs`").
			WithArgs(uint8(24), fmt.Sprintf(`%%"order_no":"%s"%%`, orderNo)).
			WillReturnRows(sqlmock.NewRows([]string{"id", "content"}).
				AddRow(1, fmt.Sprintf(`{"order_id":100,"order_no":"%s","order_status_after":7}`, orderNo)))

		got, err := HasOrderRefundLog(db, orderNo)
		if err != nil {
			t.Fatalf("HasOrderRefundLog error: %v", err)
		}
		if !got {
			t.Fatalf("HasOrderRefundLog = false, want true")
		}
		assertRefundLogExpectations(t, mock)
	})

	t.Run("returns false when audit log order_no does not match", func(t *testing.T) {
		db, mock, cleanup := newRefundLogTestDB(t)
		defer cleanup()

		mock.ExpectQuery("FROM `system_logs`").
			WithArgs(uint8(24), fmt.Sprintf(`%%"order_no":"%s"%%`, orderNo)).
			WillReturnRows(sqlmock.NewRows([]string{"id", "content"}).
				AddRow(1, `{"order_id":100,"order_no":"OTHER","order_status_after":7}`))

		got, err := HasOrderRefundLog(db, orderNo)
		if err != nil {
			t.Fatalf("HasOrderRefundLog error: %v", err)
		}
		if got {
			t.Fatalf("HasOrderRefundLog = true, want false")
		}
		assertRefundLogExpectations(t, mock)
	})

	t.Run("returns false for empty order_no without querying", func(t *testing.T) {
		db, mock, cleanup := newRefundLogTestDB(t)
		defer cleanup()

		got, err := HasOrderRefundLog(db, "")
		if err != nil {
			t.Fatalf("HasOrderRefundLog error: %v", err)
		}
		if got {
			t.Fatalf("HasOrderRefundLog = true, want false")
		}
		assertRefundLogExpectations(t, mock)
	})
}

func newRefundLogTestDB(t *testing.T) (*gorm.DB, sqlmock.Sqlmock, func()) {
	t.Helper()

	sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherFunc(func(expectedSQL, actualSQL string) error {
		if strings.Contains(actualSQL, expectedSQL) {
			return nil
		}
		return fmt.Errorf("actual sql %q does not contain %q", actualSQL, expectedSQL)
	})))
	if err != nil {
		t.Fatalf("create sqlmock: %v", err)
	}

	db, err := gorm.Open(mysql.New(mysql.Config{Conn: sqlDB, SkipInitializeWithVersion: true}), &gorm.Config{})
	if err != nil {
		_ = sqlDB.Close()
		t.Fatalf("open gorm db: %v", err)
	}

	return db, mock, func() {
		_ = sqlDB.Close()
	}
}

func assertRefundLogExpectations(t *testing.T, mock sqlmock.Sqlmock) {
	t.Helper()
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("unmet sql expectations: %v", err)
	}
}
