package main

import (
	"context"
	"database/sql"
	"flag"
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	mysqlstorage "hyapp/services/statistics-service/internal/storage/mysql"

	_ "github.com/go-sql-driver/mysql"
)

type auditWindow struct {
	startDay string
	endDay   string
	startMS  int64
	endMS    int64
}

type auditCheck struct {
	key      string
	label    string
	source   string
	required bool
	count    int64
	err      error
}

func main() {
	var (
		appCode       = flag.String("app", envDefault("APP_CODE", "lalu"), "app_code to audit")
		statTZ        = flag.String("timezone", envDefault("STAT_TZ", mysqlstorage.StatTZAsiaShanghai), "stat_tz to audit: UTC or Asia/Shanghai")
		startDay      = flag.String("start-day", envDefault("START_DAY", todayIn(mysqlstorage.StatTZAsiaShanghai)), "inclusive stat day in selected timezone, yyyy-mm-dd")
		endDay        = flag.String("end-day", envDefault("END_DAY", todayIn(mysqlstorage.StatTZAsiaShanghai)), "inclusive stat day in selected timezone, yyyy-mm-dd")
		statisticsDSN = flag.String("statistics-dsn", firstNonEmptyEnv("STATISTICS_MYSQL_DSN", "MYSQL_DSN"), "statistics mysql dsn")
		userDSN       = flag.String("user-dsn", firstNonEmptyEnv("USER_MYSQL_DSN", "MYSQL_USER_DSN"), "user-service mysql dsn")
		walletDSN     = flag.String("wallet-dsn", firstNonEmptyEnv("WALLET_MYSQL_DSN", "MYSQL_WALLET_DSN"), "wallet-service mysql dsn")
		roomDSN       = flag.String("room-dsn", firstNonEmptyEnv("ROOM_MYSQL_DSN", "MYSQL_ROOM_DSN"), "room-service mysql dsn")
		gameDSN       = flag.String("game-dsn", firstNonEmptyEnv("GAME_MYSQL_DSN", "MYSQL_GAME_DSN"), "game-service mysql dsn")
	)
	flag.Parse()

	statTZValue, loc, err := normalizeStatTZ(*statTZ)
	if err != nil {
		log.Fatal(err)
	}
	window, err := resolveWindow(*startDay, *endDay, loc)
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()
	dbs := map[string]*sql.DB{}
	for name, dsn := range map[string]string{
		"statistics": *statisticsDSN,
		"user":       *userDSN,
		"wallet":     *walletDSN,
		"room":       *roomDSN,
		"game":       *gameDSN,
	} {
		if strings.TrimSpace(dsn) == "" {
			log.Fatalf("%s dsn is required", name)
		}
		db, err := openDB(ctx, dsn)
		if err != nil {
			log.Fatalf("open %s db: %v", name, err)
		}
		defer db.Close()
		dbs[name] = db
	}

	sourceChecks := auditSources(ctx, dbs, *appCode, window)
	statsChecks := auditStatistics(ctx, dbs["statistics"], *appCode, statTZValue, window)
	failed := printChecks("owner outbox", sourceChecks)
	failed += printChecks("statistics aggregate", statsChecks)
	if failed > 0 {
		log.Fatalf("databi real flow audit failed: %d required checks have no data", failed)
	}
	fmt.Printf("databi real flow audit passed app=%s stat_tz=%s days=%s..%s\n", *appCode, statTZValue, window.startDay, window.endDay)
}

func auditSources(ctx context.Context, dbs map[string]*sql.DB, app string, window auditWindow) []auditCheck {
	return []auditCheck{
		countCheck(ctx, dbs["user"], "new_users", "新增用户", "user_outbox.UserRegistered", true, `
			SELECT COUNT(*) FROM user_outbox
			WHERE app_code = ? AND event_type = 'UserRegistered' AND created_at_ms >= ? AND created_at_ms < ?`, app, window.startMS, window.endMS),
		countCheck(ctx, dbs["room"], "active_users", "活跃用户", "room_outbox.RoomUserJoined", true, `
			SELECT COUNT(*) FROM room_outbox
			WHERE app_code = ? AND event_type = 'RoomUserJoined' AND created_at_ms >= ? AND created_at_ms < ?`, app, window.startMS, window.endMS),
		countCheck(ctx, dbs["user"], "mic_online_ms", "麦上时长", "user_outbox.UserMicDailyStatsUpdated", true, `
			SELECT COUNT(*) FROM user_outbox
			WHERE app_code = ? AND event_type = 'UserMicDailyStatsUpdated' AND created_at_ms >= ? AND created_at_ms < ?`, app, window.startMS, window.endMS),
		countCheck(ctx, dbs["wallet"], "google_recharge_usd_minor", "谷歌充值", "wallet_outbox.WalletRechargeRecorded(recharge_type=google/google_play，可本地模拟)", true, `
			SELECT COUNT(*) FROM wallet_outbox
			WHERE app_code = ? AND event_type = 'WalletRechargeRecorded' AND created_at_ms >= ? AND created_at_ms < ?
			  AND LOWER(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(payload, '$.recharge_type')), JSON_UNQUOTE(JSON_EXTRACT(payload, '$.channel')), '')) IN ('google','google_play')`, app, window.startMS, window.endMS),
		countCheck(ctx, dbs["wallet"], "mifapay_recharge_usd_minor", "三方充值", "wallet_outbox.WalletRechargeRecorded(recharge_type=mifapay/v5pay，可本地模拟)", true, `
			SELECT COUNT(*) FROM wallet_outbox
			WHERE app_code = ? AND event_type = 'WalletRechargeRecorded' AND created_at_ms >= ? AND created_at_ms < ?
			  AND LOWER(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(payload, '$.recharge_type')), JSON_UNQUOTE(JSON_EXTRACT(payload, '$.channel')), '')) IN ('mifapay','v5pay')`, app, window.startMS, window.endMS),
		countCheck(ctx, dbs["wallet"], "coin_seller_stock_coin", "币商充值金币", "wallet_outbox.WalletCoinSellerStockPurchased", true, `
			SELECT COUNT(*) FROM wallet_outbox
			WHERE app_code = ? AND event_type = 'WalletCoinSellerStockPurchased' AND created_at_ms >= ? AND created_at_ms < ?`, app, window.startMS, window.endMS),
		countCheck(ctx, dbs["wallet"], "coin_seller_transfer_coin", "币商出货金币", "wallet_outbox.WalletRechargeRecorded(recharge_type=coin_seller_transfer)", true, `
			SELECT COUNT(*) FROM wallet_outbox
			WHERE app_code = ? AND event_type = 'WalletRechargeRecorded' AND created_at_ms >= ? AND created_at_ms < ?
			  AND LOWER(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(payload, '$.recharge_type')), '')) IN ('coin_seller_transfer','coin_seller')`, app, window.startMS, window.endMS),
		countCheck(ctx, dbs["wallet"], "coin_total", "用户金币数", "wallet_entries(asset=COIN)", true, `
			SELECT COUNT(*) FROM wallet_entries e
			WHERE e.app_code = ? AND e.asset_type = 'COIN' AND e.created_at_ms < ?`, app, window.endMS),
		countCheck(ctx, dbs["wallet"], "consumed_coin", "消耗金币", "wallet_outbox.WalletBalanceChanged(COIN消费biz_type)", true, `
			SELECT COUNT(*) FROM wallet_outbox
			WHERE app_code = ? AND event_type = 'WalletBalanceChanged' AND asset_type = 'COIN' AND available_delta + frozen_delta < 0
			  AND created_at_ms >= ? AND created_at_ms < ?
			  AND LOWER(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(payload, '$.biz_type')), '')) IN ('gift_debit','direct_gift_debit','vip_purchase','resource_shop_purchase','red_packet_create','game_debit','wheel_draw_debit','cp_breakup_fee')`, app, window.startMS, window.endMS),
		countCheck(ctx, dbs["wallet"], "platform_grant_coin", "平台发放金币", "wallet_outbox 平台奖励/资源发放", true, `
			SELECT COUNT(*) FROM wallet_outbox
			WHERE app_code = ? AND created_at_ms >= ? AND created_at_ms < ?
			  AND (
			    event_type IN ('WalletTaskRewardCredited','WalletRoomTurnoverRewardCredited','WalletInviteActivityRewardCredited','WalletAgencyOpeningRewardCredited')
			    OR (
			      event_type = 'WalletBalanceChanged' AND asset_type = 'COIN' AND available_delta + frozen_delta > 0
			      AND LOWER(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(payload, '$.biz_type')), '')) = 'resource_grant'
			      AND (
			        LOWER(command_id) LIKE 'weekly_star:%' OR LOWER(command_id) LIKE 'cp_weekly_rank:%' OR LOWER(command_id) LIKE 'room_turnover_reward:%'
			        OR LOWER(command_id) LIKE 'wcheckin_%' OR LOWER(command_id) LIKE 'cmd_room_rocket_grant_%'
			        OR LOWER(command_id) LIKE 'wtask_%' OR LOWER(command_id) LIKE 'wfr_%' OR LOWER(command_id) LIKE 'wcr_%' OR LOWER(command_id) LIKE 'wreg_%'
			      )
			    )
			  )`, app, window.startMS, window.endMS),
		countCheck(ctx, dbs["wallet"], "manual_grant_coin", "人工发放金币", "wallet_outbox.WalletBalanceChanged(biz_type=manual_credit)", true, `
			SELECT COUNT(*) FROM wallet_outbox
			WHERE app_code = ? AND event_type = 'WalletBalanceChanged' AND asset_type = 'COIN' AND available_delta + frozen_delta > 0
			  AND created_at_ms >= ? AND created_at_ms < ?
			  AND LOWER(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(payload, '$.biz_type')), '')) = 'manual_credit'`, app, window.startMS, window.endMS),
		countCheck(ctx, dbs["wallet"], "salary_usd_minor", "工资总和(主播/代理)", "wallet_entries(asset=HOST_SALARY_USD/AGENCY_SALARY_USD)", true, `
			SELECT COUNT(*) FROM wallet_entries e
			WHERE e.app_code = ? AND e.asset_type IN ('HOST_SALARY_USD','AGENCY_SALARY_USD') AND e.created_at_ms < ?`, app, window.endMS),
		countCheck(ctx, dbs["wallet"], "salary_transfer_coin", "工资兑换金币", "wallet_outbox.WalletBalanceChanged(biz_type=salary_transfer_to_coin_seller)", true, `
			SELECT COUNT(*) FROM wallet_outbox
			WHERE app_code = ? AND event_type = 'WalletBalanceChanged' AND asset_type = 'COIN_SELLER_COIN'
			  AND available_delta + frozen_delta > 0 AND created_at_ms >= ? AND created_at_ms < ?
			  AND LOWER(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(payload, '$.biz_type')), '')) = 'salary_transfer_to_coin_seller'`, app, window.startMS, window.endMS),
		countCheck(ctx, dbs["room"], "gift_coin_spent", "礼物/幸运礼物流水", "room_outbox.RoomGiftSent", true, `
			SELECT COUNT(*) FROM room_outbox
			WHERE app_code = ? AND event_type = 'RoomGiftSent' AND created_at_ms >= ? AND created_at_ms < ?`, app, window.startMS, window.endMS),
		countCheck(ctx, dbs["wallet"], "lucky_gift_payout", "幸运礼物返奖", "wallet_outbox.WalletLuckyGiftRewardCredited", true, `
			SELECT COUNT(*) FROM wallet_outbox
			WHERE app_code = ? AND event_type = 'WalletLuckyGiftRewardCredited' AND created_at_ms >= ? AND created_at_ms < ?`, app, window.startMS, window.endMS),
		countCheck(ctx, dbs["game"], "game_turnover", "游戏流水/利润", "game_outbox.GameOrderSettled", true, `
			SELECT COUNT(*) FROM game_outbox
			WHERE app_code = ? AND event_type = 'GameOrderSettled' AND created_at_ms >= ? AND created_at_ms < ?`, app, window.startMS, window.endMS),
	}
}

func auditStatistics(ctx context.Context, db *sql.DB, app string, statTZ string, window auditWindow) []auditCheck {
	checks := []auditCheck{}
	for _, spec := range []struct {
		key   string
		label string
		expr  string
	}{
		{"new_users", "新增用户", "SUM(new_users)"},
		{"active_users", "活跃用户", "SUM(active_users)"},
		{"paid_users", "付费用户", "SUM(paid_users)"},
		{"recharge_usd_minor", "总充值", "SUM(recharge_usd_minor)"},
		{"coin_seller_recharge_usd_minor", "币商充值", "SUM(coin_seller_recharge_usd_minor)"},
		{"coin_seller_stock_coin", "币商充值金币", "SUM(coin_seller_stock_coin)"},
		{"coin_seller_transfer_coin", "币商出货金币", "SUM(coin_seller_transfer_coin)"},
		{"google_recharge_usd_minor", "谷歌充值", "SUM(google_recharge_usd_minor)"},
		{"mifapay_recharge_usd_minor", "三方充值", "SUM(mifapay_recharge_usd_minor)"},
		{"consumed_coin", "消耗金币", "SUM(consumed_coin)"},
		{"platform_grant_coin", "平台发放金币", "SUM(platform_grant_coin)"},
		{"manual_grant_coin", "人工发放金币", "SUM(manual_grant_coin)"},
		{"salary_transfer_coin", "工资兑换金币", "SUM(salary_transfer_coin)"},
		{"mic_online_ms", "麦上总时长", "SUM(mic_online_ms)"},
		{"mic_online_users", "平均麦上时间用户数", "SUM(mic_online_users)"},
		{"gift_coin_spent", "礼物流水", "SUM(gift_coin_spent)"},
		{"lucky_gift_turnover", "幸运礼物流水", "SUM(lucky_gift_turnover)"},
		{"lucky_gift_payout", "幸运礼物返奖", "SUM(lucky_gift_payout)"},
		{"game_turnover", "游戏流水", "SUM(game_turnover)"},
		{"game_payout", "游戏返奖", "SUM(game_payout)"},
	} {
		checks = append(checks, countCheck(ctx, db, spec.key, spec.label, "stat_app_day_country."+spec.key, true,
			"SELECT COALESCE("+spec.expr+",0) FROM stat_app_day_country WHERE app_code = ? AND stat_tz = ? AND stat_day BETWEEN ? AND ?",
			app, statTZ, window.startDay, window.endDay))
	}
	checks = append(checks,
		countCheck(ctx, db, "coin_total", "用户金币数", "stat_app_day_country.coin_total(end day)", true, `
			SELECT COALESCE(SUM(CASE WHEN stat_day = ? THEN coin_total ELSE 0 END),0)
			FROM stat_app_day_country
			WHERE app_code = ? AND stat_tz = ? AND stat_day BETWEEN ? AND ?`,
			window.endDay, app, statTZ, window.startDay, window.endDay),
		countCheck(ctx, db, "salary_usd_minor", "工资总和(主播/代理)", "stat_app_day_country.salary_usd_minor(end day)", true, `
			SELECT COALESCE(SUM(CASE WHEN stat_day = ? THEN salary_usd_minor ELSE 0 END),0)
			FROM stat_app_day_country
			WHERE app_code = ? AND stat_tz = ? AND stat_day BETWEEN ? AND ?`,
			window.endDay, app, statTZ, window.startDay, window.endDay),
		countCheck(ctx, db, "super_lucky_gift_turnover", "超级幸运礼物流水", "stat_lucky_gift_pool_day_country(pool_id LIKE %super_lucky%)", true, `
			SELECT COALESCE(SUM(turnover_coin),0) FROM stat_lucky_gift_pool_day_country
			WHERE app_code = ? AND stat_tz = ? AND stat_day BETWEEN ? AND ? AND LOWER(pool_id) LIKE '%super_lucky%'`,
			app, statTZ, window.startDay, window.endDay),
		countCheck(ctx, db, "super_lucky_gift_payout", "超级幸运礼物返奖", "stat_lucky_gift_pool_day_country(pool_id LIKE %super_lucky%)", true, `
			SELECT COALESCE(SUM(payout_coin),0) FROM stat_lucky_gift_pool_day_country
			WHERE app_code = ? AND stat_tz = ? AND stat_day BETWEEN ? AND ? AND LOWER(pool_id) LIKE '%super_lucky%'`,
			app, statTZ, window.startDay, window.endDay),
	)
	return checks
}

func countCheck(ctx context.Context, db *sql.DB, key string, label string, source string, required bool, query string, args ...any) auditCheck {
	check := auditCheck{key: key, label: label, source: source, required: required}
	if db == nil {
		check.err = fmt.Errorf("db is nil")
		return check
	}
	err := db.QueryRowContext(ctx, query, args...).Scan(&check.count)
	if err != nil {
		check.err = err
	}
	return check
}

func printChecks(title string, checks []auditCheck) int {
	fmt.Printf("\n%s\n", title)
	failed := 0
	for _, check := range checks {
		status := "OK"
		if check.err != nil || (check.required && check.count <= 0) {
			status = "MISSING"
			failed++
		}
		if check.err != nil {
			fmt.Printf("  %-8s %-34s count=%d source=%s err=%v\n", status, check.label, check.count, check.source, check.err)
			continue
		}
		fmt.Printf("  %-8s %-34s count=%d source=%s\n", status, check.label, check.count, check.source)
	}
	return failed
}

func normalizeStatTZ(value string) (string, *time.Location, error) {
	switch strings.TrimSpace(value) {
	case mysqlstorage.StatTZUTC:
		return mysqlstorage.StatTZUTC, time.UTC, nil
	case mysqlstorage.StatTZAsiaShanghai:
		return mysqlstorage.StatTZAsiaShanghai, time.FixedZone(mysqlstorage.StatTZAsiaShanghai, 8*60*60), nil
	default:
		return "", nil, fmt.Errorf("unsupported stat_tz %q; only UTC and Asia/Shanghai are supported", value)
	}
}

func resolveWindow(startDay string, endDay string, loc *time.Location) (auditWindow, error) {
	start, err := time.ParseInLocation("2006-01-02", strings.TrimSpace(startDay), loc)
	if err != nil {
		return auditWindow{}, fmt.Errorf("parse start-day: %w", err)
	}
	end, err := time.ParseInLocation("2006-01-02", strings.TrimSpace(endDay), loc)
	if err != nil {
		return auditWindow{}, fmt.Errorf("parse end-day: %w", err)
	}
	if end.Before(start) {
		return auditWindow{}, fmt.Errorf("end-day %s is before start-day %s", endDay, startDay)
	}
	return auditWindow{
		startDay: start.Format("2006-01-02"),
		endDay:   end.Format("2006-01-02"),
		startMS:  start.UnixMilli(),
		endMS:    end.AddDate(0, 0, 1).UnixMilli(),
	}, nil
}

func openDB(ctx context.Context, dsn string) (*sql.DB, error) {
	db, err := sql.Open("mysql", strings.TrimSpace(dsn))
	if err != nil {
		return nil, err
	}
	if err := db.PingContext(ctx); err != nil {
		_ = db.Close()
		return nil, err
	}
	return db, nil
}

func todayIn(statTZ string) string {
	_, loc, err := normalizeStatTZ(statTZ)
	if err != nil {
		loc = time.FixedZone(mysqlstorage.StatTZAsiaShanghai, 8*60*60)
	}
	return time.Now().In(loc).Format("2006-01-02")
}

func envDefault(key string, fallback string) string {
	if value := strings.TrimSpace(os.Getenv(key)); value != "" {
		return value
	}
	return fallback
}

func firstNonEmptyEnv(keys ...string) string {
	for _, key := range keys {
		if value := strings.TrimSpace(os.Getenv(key)); value != "" {
			return value
		}
	}
	return ""
}
