package hostwithdrawal

import (
	"os"
	"reflect"
	"strings"
	"testing"

	"github.com/gin-gonic/gin"
)

func TestNormalizeListQueryMapsIdentityAndRejectsBadInput(t *testing.T) {
	query, err := normalizeListQuery(listQuery{
		Page:       -1,
		PageSize:   1000,
		AppCode:    " LaLu ",
		RecordType: "all",
		Identity:   "BD_LEADER",
		Status:     "all",
		Keyword:    "  168557  ",
		StartAtMS:  100,
		EndAtMS:    200,
	})
	if err != nil {
		t.Fatalf("normalize query failed: %v", err)
	}
	if query.Page != 1 || query.PageSize != 100 || query.AppCode != "lalu" || query.RecordType != "" || query.Status != "" || query.Keyword != "168557" {
		t.Fatalf("normalized basic fields mismatch: %+v", query)
	}
	if query.SalaryAssetType != assetAdminSalary || query.Identity != "bd_leader_admin" {
		t.Fatalf("identity mapping mismatch: %+v", query)
	}

	if _, err := normalizeListQuery(listQuery{Status: "done"}); err == nil {
		t.Fatal("expected invalid status to fail")
	}
	if _, err := normalizeListQuery(listQuery{StartAtMS: 200, EndAtMS: 200}); err == nil {
		t.Fatal("expected closed time range to fail")
	}
}

func TestSalaryTransferWhereUsesIdentityStatusTimeAndKeywordUsers(t *testing.T) {
	where, args := salaryTransferWhere(listQuery{
		AppCode:         "lalu",
		SalaryAssetType: assetHostSalary,
		Keyword:         "cmd",
		StartAtMS:       100,
		EndAtMS:         200,
	}, []int64{11, 22})

	wantWhere := "WHERE e.app_code = ? AND e.asset_type = ? AND wt.biz_type = ? AND wt.status = ? AND JSON_UNQUOTE(JSON_EXTRACT(wt.metadata_json, '$.salary_asset_type')) = ? AND e.created_at_ms >= ? AND e.created_at_ms < ? AND (e.user_id IN (?,?) OR e.counterparty_user_id IN (?,?) OR e.transaction_id LIKE ? OR wt.command_id LIKE ? OR CAST(e.user_id AS CHAR) LIKE ? OR CAST(e.counterparty_user_id AS CHAR) LIKE ?)"
	if where != wantWhere {
		t.Fatalf("where mismatch:\nwant %s\n got %s", wantWhere, where)
	}
	wantArgs := []any{"lalu", assetCoinSellerCoin, bizSalaryTransfer, transactionSucceeded, assetHostSalary, int64(100), int64(200), int64(11), int64(22), int64(11), int64(22), "%cmd%", "%cmd%", "%cmd%", "%cmd%"}
	if !reflect.DeepEqual(args, wantArgs) {
		t.Fatalf("args mismatch:\nwant %#v\n got %#v", wantArgs, args)
	}
}

func TestWithdrawalWhereUsesStatusIdentityTimeAndKeywordUsers(t *testing.T) {
	where, args := withdrawalWhere(listQuery{
		AppCode:         "lalu",
		SalaryAssetType: assetBDSalary,
		Status:          statusPending,
		Keyword:         "upi",
		StartAtMS:       100,
		EndAtMS:         200,
	}, []int64{77})

	wantWhere := "WHERE a.app_code = ? AND a.salary_asset_type = ? AND a.status = ? AND a.created_at_ms >= ? AND a.created_at_ms < ? AND (a.user_id IN (?) OR CAST(a.id AS CHAR) LIKE ? OR a.user_id LIKE ? OR a.withdraw_method LIKE ? OR a.withdraw_address LIKE ? OR a.freeze_transaction_id LIKE ? OR a.audit_transaction_id LIKE ? OR a.approver_name LIKE ?)"
	if where != wantWhere {
		t.Fatalf("where mismatch:\nwant %s\n got %s", wantWhere, where)
	}
	wantArgs := []any{"lalu", assetBDSalary, statusPending, int64(100), int64(200), "77", "%upi%", "%upi%", "%upi%", "%upi%", "%upi%", "%upi%", "%upi%"}
	if !reflect.DeepEqual(args, wantArgs) {
		t.Fatalf("args mismatch:\nwant %#v\n got %#v", wantArgs, args)
	}
}

func TestPageMergedItemsSortsAndSlicesStableMixedSources(t *testing.T) {
	items := []itemDTO{
		{ID: "salary_transfer:8", RecordType: recordTypeSalaryTransfer, CreatedAtMS: 100},
		{ID: "withdrawal:7", RecordType: recordTypeWithdrawal, ApplicationID: "7", CreatedAtMS: 100},
		{ID: "salary_transfer:9", RecordType: recordTypeSalaryTransfer, CreatedAtMS: 100},
		{ID: "withdrawal:11", RecordType: recordTypeWithdrawal, ApplicationID: "11", CreatedAtMS: 90},
		{ID: "salary_transfer:1", RecordType: recordTypeSalaryTransfer, CreatedAtMS: 80},
	}

	page := pageMergedItems(items, 1, 4)
	gotIDs := []string{page[0].ID, page[1].ID, page[2].ID, page[3].ID}
	wantIDs := []string{"salary_transfer:9", "salary_transfer:8", "withdrawal:7", "withdrawal:11"}
	if !reflect.DeepEqual(gotIDs, wantIDs) {
		t.Fatalf("page sort mismatch:\nwant %#v\n got %#v", wantIDs, gotIDs)
	}

	secondPage := pageMergedItems(items, 2, 2)
	gotIDs = []string{secondPage[0].ID, secondPage[1].ID}
	wantIDs = []string{"withdrawal:7", "withdrawal:11"}
	if !reflect.DeepEqual(gotIDs, wantIDs) {
		t.Fatalf("second page mismatch:\nwant %#v\n got %#v", wantIDs, gotIDs)
	}
}

func TestRegisterRoutesExposesHostWithdrawalsEndpoint(t *testing.T) {
	gin.SetMode(gin.TestMode)
	engine := gin.New()
	group := engine.Group("/api/v1")
	RegisterRoutes(group, &Handler{})

	for _, route := range engine.Routes() {
		if route.Method == "GET" && route.Path == "/api/v1/admin/host-withdrawals" {
			return
		}
	}
	t.Fatal("host withdrawals route was not registered")
}

func TestHostWithdrawalIndexSQLKeepsRequiredReadPaths(t *testing.T) {
	adminSQL, err := os.ReadFile("../../../migrations/072_host_withdrawal_navigation.sql")
	if err != nil {
		t.Fatalf("read admin migration failed: %v", err)
	}
	walletSQL, err := os.ReadFile("../../../../../scripts/mysql/054_wallet_entries_asset_counterparty_time_index.sql")
	if err != nil {
		t.Fatalf("read wallet migration failed: %v", err)
	}
	for _, snippet := range []string{
		"idx_admin_withdrawal_app_time (app_code, created_at_ms, id)",
		"idx_admin_withdrawal_app_asset_time (app_code, salary_asset_type, created_at_ms, id)",
		"idx_admin_withdrawal_app_status_asset_time (app_code, status, salary_asset_type, created_at_ms, id)",
		"idx_admin_withdrawal_app_user_time (app_code, user_id, created_at_ms, id)",
	} {
		if !strings.Contains(string(adminSQL), snippet) {
			t.Fatalf("admin migration missing index snippet %q", snippet)
		}
	}
	if !strings.Contains(string(walletSQL), "idx_wallet_entries_asset_counterparty_time (app_code, asset_type, counterparty_user_id, created_at_ms, entry_id)") {
		t.Fatal("wallet migration missing counterparty index")
	}
}
