package financeorder

import (
	"context"
	"database/sql"
	"errors"
	"strings"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"hyapp-admin-server/internal/config"
)

func TestLegacyCoinSellerRechargeWriterGrantUpdatesBalanceAndRunningWater(t *testing.T) {
	walletDB, walletMock, cleanup := newLegacyWriterSQLMock(t)
	defer cleanup()
	writer := NewMySQLLegacyCoinSellerRechargeWriter(config.LegacyCoinSellerRechargeSourceConfig{
		AppCode:        "aslan",
		SysOrigin:      "ATYOU",
		RequestTimeout: 5 * time.Second,
	}, nil, walletDB)
	grantedAt := time.Date(2026, 7, 9, 9, 30, 0, 0, time.UTC)

	walletMock.ExpectBegin()
	expectLegacyBalanceLock(walletMock, "ATYOU", 10001).
		WillReturnRows(sqlmock.NewRows([]string{"id", "earn_points", "consumption_points"}).AddRow(int64(77), "5000", "1000"))
	walletMock.ExpectQuery(`(?s)SELECT id\s+FROM user_freight_balance_running_water\s+WHERE sys_origin = \? AND remark = \?\s+LIMIT 1`).
		WithArgs("ATYOU", "MIFA-001").
		WillReturnError(sql.ErrNoRows)
	walletMock.ExpectExec(`(?s)UPDATE user_freight_balance\s+SET earn_points = COALESCE\(earn_points, 0\) \+ \?, update_user = \?, update_time = \?\s+WHERE id = \?`).
		WithArgs(int64(1200), int64(10001), grantedAt, int64(77)).
		WillReturnResult(sqlmock.NewResult(0, 1))
	walletMock.ExpectExec(`(?s)INSERT INTO user_freight_balance_running_water`).
		WithArgs(sqlmock.AnyArg(), "ATYOU", int64(10001), int64(10001), nil, int64(1200), int64(5200), "12.34", legacyCoinSellerRechargeType, "MIFA-001", int64(10001), int64(10001), grantedAt, grantedAt).
		WillReturnResult(sqlmock.NewResult(0, 1))
	walletMock.ExpectCommit()

	result, err := writer.GrantCoinSellerRecharge(context.Background(), legacyRechargeGrantInput{
		UserID:          10001,
		ExternalOrderNo: "MIFA-001",
		ProviderCode:    "mifapay",
		USDAmount:       "12.34",
		CoinAmount:      1200,
		GrantedAt:       grantedAt,
	})
	if err != nil {
		t.Fatalf("legacy grant failed: %v", err)
	}
	if !strings.HasPrefix(result.TransactionID, "legacy-freight-purchase-") || result.BalanceAfter != 5200 {
		t.Fatalf("legacy grant result mismatch: %+v", result)
	}
	if err := walletMock.ExpectationsWereMet(); err != nil {
		t.Fatalf("wallet sql expectations: %v", err)
	}
}

func TestLegacyCoinSellerRechargeWriterGrantRejectsDuplicateOrder(t *testing.T) {
	walletDB, walletMock, cleanup := newLegacyWriterSQLMock(t)
	defer cleanup()
	writer := NewMySQLLegacyCoinSellerRechargeWriter(config.LegacyCoinSellerRechargeSourceConfig{
		AppCode:        "yumi",
		SysOrigin:      "LIKEI",
		RequestTimeout: 5 * time.Second,
	}, nil, walletDB)

	walletMock.ExpectBegin()
	expectLegacyBalanceLock(walletMock, "LIKEI", 20002).
		WillReturnRows(sqlmock.NewRows([]string{"id", "earn_points", "consumption_points"}).AddRow(int64(88), "7000", "0"))
	walletMock.ExpectQuery(`(?s)SELECT id\s+FROM user_freight_balance_running_water\s+WHERE sys_origin = \? AND remark = \?\s+LIMIT 1`).
		WithArgs("LIKEI", "V5-001").
		WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(int64(9001)))
	walletMock.ExpectRollback()

	_, err := writer.GrantCoinSellerRecharge(context.Background(), legacyRechargeGrantInput{
		UserID:          20002,
		ExternalOrderNo: "V5-001",
		ProviderCode:    "v5pay",
		USDAmount:       "10.00",
		CoinAmount:      1000,
		GrantedAt:       time.Date(2026, 7, 9, 10, 0, 0, 0, time.UTC),
	})
	if !errors.Is(err, errLegacyCoinSellerOrderDuplicate) {
		t.Fatalf("expected duplicate order error, got %v", err)
	}
	if err := walletMock.ExpectationsWereMet(); err != nil {
		t.Fatalf("wallet sql expectations: %v", err)
	}
}

func TestLegacyCoinSellerRechargeWriterResolveCoinSellerByAccountRequiresDealer(t *testing.T) {
	appDB, appMock, cleanupApp := newLegacyWriterSQLMock(t)
	defer cleanupApp()
	walletDB, walletMock, cleanupWallet := newLegacyWriterSQLMock(t)
	defer cleanupWallet()
	writer := NewMySQLLegacyCoinSellerRechargeWriter(config.LegacyCoinSellerRechargeSourceConfig{
		AppCode:        "aslan",
		SysOrigin:      "ATYOU",
		RequestTimeout: 5 * time.Second,
	}, appDB, walletDB)

	expectLegacyUserLookup(appMock, "ATYOU", "agent001").
		WillReturnRows(sqlmock.NewRows([]string{"id", "account", "country_id", "country_code"}).AddRow(int64(10001), "agent001", int64(63), "ph"))
	expectLegacyCoinSellerCheck(walletMock, "ATYOU", 10001).
		WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(int64(77)))

	target, err := writer.ResolveCoinSeller(context.Background(), " agent001 ")
	if err != nil {
		t.Fatalf("resolve legacy coin seller failed: %v", err)
	}
	if target.UserID != 10001 || target.DisplayUserID != "agent001" || target.CountryID != 63 || target.CountryCode != "PH" {
		t.Fatalf("legacy coin seller target mismatch: %+v", target)
	}
	if err := appMock.ExpectationsWereMet(); err != nil {
		t.Fatalf("app sql expectations: %v", err)
	}
	if err := walletMock.ExpectationsWereMet(); err != nil {
		t.Fatalf("wallet sql expectations: %v", err)
	}
}

func TestLegacyCoinSellerRechargeWriterResolveCoinSellerRejectsNonDealer(t *testing.T) {
	appDB, appMock, cleanupApp := newLegacyWriterSQLMock(t)
	defer cleanupApp()
	walletDB, walletMock, cleanupWallet := newLegacyWriterSQLMock(t)
	defer cleanupWallet()
	writer := NewMySQLLegacyCoinSellerRechargeWriter(config.LegacyCoinSellerRechargeSourceConfig{
		AppCode:        "yumi",
		SysOrigin:      "LIKEI",
		RequestTimeout: 5 * time.Second,
	}, appDB, walletDB)

	expectLegacyUserLookup(appMock, "LIKEI", "visitor001").
		WillReturnRows(sqlmock.NewRows([]string{"id", "account", "country_id", "country_code"}).AddRow(int64(20002), "visitor001", int64(86), "ID"))
	expectLegacyCoinSellerCheck(walletMock, "LIKEI", 20002).
		WillReturnError(sql.ErrNoRows)

	_, err := writer.ResolveCoinSeller(context.Background(), "visitor001")
	if !errors.Is(err, errLegacyCoinSellerNotFound) {
		t.Fatalf("expected non-dealer legacy user to be rejected, got %v", err)
	}
	if err := appMock.ExpectationsWereMet(); err != nil {
		t.Fatalf("app sql expectations: %v", err)
	}
	if err := walletMock.ExpectationsWereMet(); err != nil {
		t.Fatalf("wallet sql expectations: %v", err)
	}
}

func expectLegacyBalanceLock(mock sqlmock.Sqlmock, sysOrigin string, userID int64) *sqlmock.ExpectedQuery {
	return mock.ExpectQuery(`(?s)SELECT id, CAST\(COALESCE\(earn_points, 0\) AS CHAR\), CAST\(COALESCE\(consumption_points, 0\) AS CHAR\)\s+FROM user_freight_balance\s+WHERE sys_origin = \? AND user_id = \?.*FOR UPDATE`).
		WithArgs(sysOrigin, userID)
}

func expectLegacyUserLookup(mock sqlmock.Sqlmock, sysOrigin string, keyword string) *sqlmock.ExpectedQuery {
	return mock.ExpectQuery(`(?s)SELECT id, account, COALESCE\(country_id, 0\), COALESCE\(country_code, ''\)\s+FROM user_base_info\s+WHERE origin_sys = \? AND \(account = \? OR CAST\(id AS CHAR\) = \?\).*LIMIT 1`).
		WithArgs(sysOrigin, keyword, keyword)
}

func expectLegacyCoinSellerCheck(mock sqlmock.Sqlmock, sysOrigin string, userID int64) *sqlmock.ExpectedQuery {
	return mock.ExpectQuery(`(?s)SELECT id\s+FROM user_freight_balance\s+WHERE sys_origin = \? AND user_id = \? AND COALESCE\(is_close, 0\) = 0 AND \(COALESCE\(dealer, 0\) = 1 OR COALESCE\(super_dealer, 0\) = 1\)\s+LIMIT 1`).
		WithArgs(sysOrigin, userID)
}

func newLegacyWriterSQLMock(t *testing.T) (*sql.DB, sqlmock.Sqlmock, func()) {
	t.Helper()
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("new sqlmock: %v", err)
	}
	return db, mock, func() {
		_ = db.Close()
	}
}
