import {
  Injectable,
} from '@nestjs/common';
import { PrismaService } from '../../shared/prisma/prisma.service';
import { MarketsService } from '../odds/markets.service';
import { WC2026_LEAGUE_CODE, WC2026_OUTRIGHT_TEAMS } from './wc2026-outright-teams';
import { syncWc2026OutrightMarket } from './wc2026-outright.sync';
import { appBadRequest, appNotFound } from '../../shared/common/app-error';

const PLACEHOLDER_TEAM_CODE = 'OUT';
const OUTRIGHT_MARKET_TYPE = 'OUTRIGHT_WINNER';

@Injectable()
export class OutrightService {
  constructor(
    private prisma: PrismaService,
    private markets: MarketsService,
  ) {}

  async listForAdmin() {
    const matches = await this.prisma.match.findMany({
      where: { isOutright: true, sportType: 'FOOTBALL', deletedAt: null },
      include: {
        league: true,
        markets: {
          where: { marketType: OUTRIGHT_MARKET_TYPE },
          select: {
            id: true,
            status: true,
            _count: {
              select: {
                selections: {
                  where: {
                    status: { not: 'CLOSED' },
                    selectionCode: { not: PLACEHOLDER_TEAM_CODE },
                  },
                },
              },
            },
            selections: {
              where: {
                status: 'OPEN',
                selectionCode: { not: PLACEHOLDER_TEAM_CODE },
              },
              select: { id: true },
              take: 1,
            },
          },
        },
      },
      orderBy: [{ displayOrder: 'asc' }, { startTime: 'asc' }],
    });

    return Promise.all(
      matches.map(async (m) => {
        const league = m.league;
        const leagueCode = league.code;
        const [leagueZh, leagueEn] = await Promise.all([
          this.getTranslation('LEAGUE', league.id, 'zh-CN'),
          this.getTranslation('LEAGUE', league.id, 'en-US'),
        ]);
        const market = m.markets[0];
        const openCount = market?.selections.length ?? 0;
        const selectionCount = market?._count.selections ?? 0;
        const visibility = this.playerVisibilityByCounts(
          m.status,
          market,
          openCount,
          league.isActive,
        );
        const [titleZh, titleEn, titleMs] = await Promise.all([
          this.getOutrightTitle(m.id, 'zh-CN'),
          this.getOutrightTitle(m.id, 'en-US'),
          this.getOutrightTitle(m.id, 'ms-MY'),
        ]);
        return {
          id: m.id.toString(),
          leagueId: league.id.toString(),
          leagueCode,
          leagueZh,
          leagueEn,
          matchName: m.matchName ?? '',
          titleZh: titleZh || m.matchName || '',
          titleEn: titleEn || m.matchName || '',
          titleMs,
          status: m.status,
          selectionCount,
          canImportCanonical: leagueCode === WC2026_LEAGUE_CODE,
          playerVisible: visibility.playerVisible,
          playerHiddenReason: visibility.playerHiddenReason,
        };
      }),
    );
  }

  async listLeagueOptions() {
    const leagues = await this.prisma.league.findMany({
      where: { isActive: true, deletedAt: null, sportType: 'FOOTBALL' },
      orderBy: { displayOrder: 'asc' },
    });
    return Promise.all(
      leagues.map(async (l) => ({
        id: l.id.toString(),
        code: l.code,
        nameZh: await this.getTranslation('LEAGUE', l.id, 'zh-CN'),
        nameEn: await this.getTranslation('LEAGUE', l.id, 'en-US'),
      })),
    );
  }

  async getForAdmin(matchId: bigint) {
    const match = await this.getOutrightMatchOrThrow(matchId);
    const market = await this.ensureOutrightMarket(match.id);
    const league = await this.prisma.league.findUniqueOrThrow({
      where: { id: match.leagueId },
    });

    const [leagueZh, leagueEn] = await Promise.all([
      this.getTranslation('LEAGUE', match.leagueId, 'zh-CN'),
      this.getTranslation('LEAGUE', match.leagueId, 'en-US'),
    ]);

    const fullMarket = await this.prisma.market.findUniqueOrThrow({
      where: { id: market.id },
      include: {
        selections: { orderBy: { sortOrder: 'asc' } },
      },
    });

    const selections = await Promise.all(
      fullMarket.selections
        .filter((s) => s.selectionCode !== PLACEHOLDER_TEAM_CODE)
        .map(async (sel, index) => {
          const team = await this.prisma.team.findUnique({
            where: { code: sel.selectionCode },
          });
          const teamZh = team
            ? await this.getTranslation('TEAM', team.id, 'zh-CN')
            : sel.selectionName;
          const teamEn = team
            ? await this.getTranslation('TEAM', team.id, 'en-US')
            : sel.selectionName;
          return {
            id: sel.id.toString(),
            teamId: team?.id.toString() ?? null,
            teamCode: sel.selectionCode,
            rank: sel.sortOrder + 1 || index + 1,
            teamZh: teamZh || sel.selectionName,
            teamEn: teamEn || sel.selectionName,
            logoUrl: team?.logoUrl ?? null,
            odds: sel.odds.toString(),
            oddsVersion: sel.oddsVersion.toString(),
            status: sel.status,
          };
        }),
    );

    const visibility = this.playerVisibility(
      match.status,
      fullMarket,
      fullMarket.selections.filter(
        (s) => s.selectionCode !== PLACEHOLDER_TEAM_CODE,
      ),
      league.isActive,
    );

    const unsettledFixtureCount = await this.countUnsettledLeagueFixtures(
      match.leagueId,
    );

    const [titleZh, titleEn, titleMs] = await Promise.all([
      this.getOutrightTitle(match.id, 'zh-CN'),
      this.getOutrightTitle(match.id, 'en-US'),
      this.getOutrightTitle(match.id, 'ms-MY'),
    ]);

    return {
      id: match.id.toString(),
      leagueId: match.leagueId.toString(),
      leagueCode: league.code,
      leagueZh,
      leagueEn,
      matchName: match.matchName ?? '',
      titleZh: titleZh || match.matchName || '',
      titleEn: titleEn || match.matchName || '',
      titleMs,
      status: match.status,
      leagueIsPublished: league.isActive,
      unsettledFixtureCount,
      marketId: fullMarket.id.toString(),
      marketStatus: fullMarket.status,
      canImportCanonical: league.code === WC2026_LEAGUE_CODE,
      expectedCanonicalCount:
        league.code === WC2026_LEAGUE_CODE ? WC2026_OUTRIGHT_TEAMS.length : null,
      playerVisible: visibility.playerVisible,
      playerHiddenReason: visibility.playerHiddenReason,
      selections,
    };
  }

  /** 按联赛获取或创建冠军盘，并从单场赛程同步参赛队伍 */
  async getOrCreateAndSyncForLeague(leagueId: bigint) {
    const league = await this.prisma.league.findUnique({
      where: { id: leagueId },
    });
    if (!league) throw appNotFound('LEAGUE_NOT_FOUND');

    let match = await this.prisma.match.findFirst({
      where: { leagueId, isOutright: true, deletedAt: null },
      orderBy: { id: 'asc' },
    });
    if (!match) {
      const [leagueZh, leagueEn, leagueMs] = await Promise.all([
        this.getTranslation('LEAGUE', leagueId, 'zh-CN'),
        this.getTranslation('LEAGUE', leagueId, 'en-US'),
        this.getTranslation('LEAGUE', leagueId, 'ms-MY'),
      ]);
      await this.createForAdmin({
        leagueId,
        titleZh: leagueZh || league.code,
        titleEn: leagueEn || league.code,
        titleMs: leagueMs || undefined,
        status: league.isActive ? 'PUBLISHED' : 'DRAFT',
      });
      match = await this.prisma.match.findFirstOrThrow({
        where: { leagueId, isOutright: true, deletedAt: null },
        orderBy: { id: 'asc' },
      });
    } else {
      await this.syncOutrightStatusWithLeague(match, league);
      match = await this.prisma.match.findFirstOrThrow({
        where: { id: match.id },
      });
    }
    const sync = await this.syncSelectionsFromLeagueFixtures(match.id);
    const data = await this.getForAdmin(match.id);
    return {
      ...data,
      fixtureSyncAdded: sync.addedCount,
      fixtureSyncReopened: sync.reopenedCount,
    };
  }

  /** 联赛发布后同步冠军盘状态（随联赛发布，无需单独发布） */
  async syncWithLeaguePublished(leagueId: bigint) {
    const league = await this.prisma.league.findUnique({
      where: { id: leagueId },
    });
    if (!league?.isActive) return;

    const existing = await this.prisma.match.findFirst({
      where: { leagueId, isOutright: true, deletedAt: null },
      orderBy: { id: 'asc' },
    });
    if (!existing) {
      await this.getOrCreateAndSyncForLeague(leagueId);
      return;
    }
    await this.syncOutrightStatusWithLeague(existing, league);
  }

  /** 联赛下尚未结算/取消的单场数量（不含冠军盘） */
  async countUnsettledLeagueFixtures(leagueId: bigint): Promise<number> {
    return this.prisma.match.count({
      where: {
        leagueId,
        isOutright: false,
        deletedAt: null,
        status: { notIn: ['SETTLED', 'CANCELLED'] },
      },
    });
  }

  private async syncOutrightStatusWithLeague(
    match: { id: bigint; status: string },
    league: { isActive: boolean },
  ) {
    if (!league.isActive || match.status !== 'DRAFT') return;
    await this.prisma.match.update({
      where: { id: match.id },
      data: { status: 'PUBLISHED', publishTime: new Date() },
    });
  }

  /** 若联赛已有冠军盘，则从单场同步球队（不自动创建冠军盘） */
  async syncOutrightTeamsForLeagueIfExists(leagueId: bigint) {
    const match = await this.prisma.match.findFirst({
      where: { leagueId, isOutright: true, deletedAt: null },
      orderBy: { id: 'asc' },
    });
    if (!match) return { addedCount: 0, reopenedCount: 0 };
    return this.syncSelectionsFromLeagueFixtures(match.id);
  }

  async syncSelectionsFromLeagueFixtures(matchId: bigint) {
    const match = await this.getOutrightMatchOrThrow(matchId);
    const market = await this.ensureOutrightMarket(match.id);
    const teams = await this.collectFixtureTeamsForLeague(match.leagueId);
    const fixtureCodes = new Set(teams.map((t) => t.code));

    const existing = await this.prisma.marketSelection.findMany({
      where: {
        marketId: market.id,
        selectionCode: { not: PLACEHOLDER_TEAM_CODE },
      },
    });

    const existingCodes = new Set(existing.map((s) => s.selectionCode));
    let sortOrder = existing.reduce((max, s) => Math.max(max, s.sortOrder), -1);
    let addedCount = 0;
    let reopenedCount = 0;

    for (const sel of existing) {
      if (fixtureCodes.has(sel.selectionCode) && sel.status === 'CLOSED') {
        await this.prisma.marketSelection.update({
          where: { id: sel.id },
          data: { status: 'OPEN' },
        });
        reopenedCount += 1;
      }
    }

    for (const team of teams) {
      if (existingCodes.has(team.code)) continue;
      const [teamZh, teamEn] = await Promise.all([
        this.getTranslation('TEAM', team.id, 'zh-CN'),
        this.getTranslation('TEAM', team.id, 'en-US'),
      ]);
      sortOrder += 1;
      await this.prisma.marketSelection.create({
        data: {
          marketId: market.id,
          selectionCode: team.code,
          selectionName: teamZh || teamEn || team.code,
          odds: 10,
          sortOrder,
          status: 'OPEN',
        },
      });
      addedCount += 1;
    }

    return { addedCount, reopenedCount };
  }

  private async collectFixtureTeamsForLeague(leagueId: bigint) {
    const matches = await this.prisma.match.findMany({
      where: { leagueId, isOutright: false, deletedAt: null },
      select: { homeTeamId: true, awayTeamId: true },
    });
    const teamIds = [
      ...new Set(matches.flatMap((m) => [m.homeTeamId, m.awayTeamId])),
    ];
    if (teamIds.length === 0) return [];
    return this.prisma.team.findMany({
      where: {
        id: { in: teamIds },
        code: { not: PLACEHOLDER_TEAM_CODE },
      },
      orderBy: { code: 'asc' },
    });
  }

  async createForAdmin(data: {
    leagueId: bigint;
    titleZh: string;
    titleEn: string;
    titleMs?: string;
    status?: string;
    isHot?: boolean;
    displayOrder?: number;
    startTime?: Date;
  }) {
    const league = await this.prisma.league.findUnique({
      where: { id: data.leagueId },
    });
    if (!league) throw appNotFound('LEAGUE_NOT_FOUND');

    const placeholder = await this.ensurePlaceholderTeam();
    const status = data.status ?? 'PUBLISHED';
    const matchName = this.resolveOutrightMatchName(data);
    const match = await this.prisma.match.create({
      data: {
        leagueId: data.leagueId,
        homeTeamId: placeholder.id,
        awayTeamId: placeholder.id,
        isOutright: true,
        matchName,
        startTime: data.startTime ?? new Date('2030-01-01T00:00:00Z'),
        status,
        publishTime: status === 'PUBLISHED' ? new Date() : undefined,
        isHot: data.isHot ?? false,
        displayOrder: data.displayOrder ?? 0,
      },
    });

    await this.upsertOutrightTitles(match.id, {
      zh: data.titleZh,
      en: data.titleEn,
      ms: data.titleMs,
    });
    await this.ensureOutrightMarket(match.id);
    return this.getForAdmin(match.id);
  }

  async updateForAdmin(
    matchId: bigint,
    data: {
      status?: string;
      matchName?: string;
      isHot?: boolean;
      displayOrder?: number;
      titleZh?: string;
      titleEn?: string;
      titleMs?: string;
    },
  ) {
    const match = await this.getOutrightMatchOrThrow(matchId);
    const status = data.status ?? match.status;

    let matchName = data.matchName?.trim();
    const titlesTouched =
      data.titleZh !== undefined ||
      data.titleEn !== undefined ||
      data.titleMs !== undefined;
    if (titlesTouched) {
      const [curZh, curEn, curMs] = await Promise.all([
        this.getOutrightTitle(matchId, 'zh-CN'),
        this.getOutrightTitle(matchId, 'en-US'),
        this.getOutrightTitle(matchId, 'ms-MY'),
      ]);
      const zh = data.titleZh !== undefined ? data.titleZh : curZh;
      const en = data.titleEn !== undefined ? data.titleEn : curEn;
      const ms = data.titleMs !== undefined ? data.titleMs : curMs;
      await this.upsertOutrightTitles(matchId, { zh, en, ms });
      matchName = this.resolveOutrightMatchName({ titleZh: zh, titleEn: en, titleMs: ms });
    }

    await this.prisma.match.update({
      where: { id: matchId },
      data: {
        status,
        matchName: matchName !== undefined ? matchName : undefined,
        isHot: data.isHot,
        displayOrder: data.displayOrder,
        publishTime:
          status === 'PUBLISHED' && !match.publishTime
            ? new Date()
            : match.publishTime,
      },
    });
    return this.getForAdmin(matchId);
  }

  async addSelection(
    matchId: bigint,
    data: {
      teamCode: string;
      teamZh: string;
      teamEn: string;
      odds: number;
      logoUrl?: string;
    },
  ) {
    if (!data.teamCode?.trim()) {
      throw appBadRequest('TEAM_CODE_REQUIRED');
    }
    if (data.odds <= 1) {
      throw appBadRequest('ODDS_MIN');
    }

    const match = await this.getOutrightMatchOrThrow(matchId);
    const market = await this.ensureOutrightMarket(match.id);
    const code = data.teamCode.trim().toUpperCase();

    const logoUrl =
      data.logoUrl === undefined
        ? undefined
        : data.logoUrl.trim()
          ? data.logoUrl.trim()
          : null;
    const team = await this.prisma.team.upsert({
      where: { code },
      create: {
        code,
        ...(logoUrl !== undefined ? { logoUrl } : {}),
      },
      update: logoUrl !== undefined ? { logoUrl } : {},
    });
    await this.upsertTeamTranslations(team.id, {
      'zh-CN': data.teamZh.trim() || data.teamEn,
      'en-US': data.teamEn.trim() || data.teamZh,
    });

    const existing = await this.prisma.marketSelection.findFirst({
      where: { marketId: market.id, selectionCode: code },
    });
    if (existing) {
      if (existing.status === 'CLOSED') {
        await this.prisma.marketSelection.update({
          where: { id: existing.id },
          data: {
            status: 'OPEN',
            odds: data.odds,
            selectionName: data.teamZh.trim() || data.teamEn,
          },
        });
        return this.getForAdmin(matchId);
      }
      throw appBadRequest('OUTRIGHT_SELECTION_EXISTS');
    }

    const maxSort = await this.prisma.marketSelection.aggregate({
      where: { marketId: market.id },
      _max: { sortOrder: true },
    });

    await this.prisma.marketSelection.create({
      data: {
        marketId: market.id,
        selectionCode: code,
        selectionName: data.teamZh.trim() || data.teamEn,
        odds: data.odds,
        sortOrder: (maxSort._max.sortOrder ?? -1) + 1,
        status: 'OPEN',
      },
    });

    return this.getForAdmin(matchId);
  }

  async addSelectionsBatch(
    matchId: bigint,
    items: Array<{
      teamCode: string;
      teamZh: string;
      teamEn: string;
      odds: number;
      logoUrl?: string;
    }>,
  ) {
    if (!items.length) {
      throw appBadRequest('OUTRIGHT_TEAMS_REQUIRED');
    }
    let added = 0;
    let skipped = 0;
    for (const item of items) {
      try {
        await this.addSelection(matchId, item);
        added += 1;
      } catch (e) {
        const msg = e instanceof Error ? e.message : '';
        if (msg.includes('already exists')) {
          skipped += 1;
          continue;
        }
        throw e;
      }
    }
    const data = await this.getForAdmin(matchId);
    return { ...data, batchResult: { added, skipped } };
  }

  async updateSelectionTeam(
    matchId: bigint,
    selectionId: bigint,
    data: {
      teamCode?: string;
      teamZh?: string;
      teamEn?: string;
      logoUrl?: string | null;
    },
  ) {
    const match = await this.getOutrightMatchOrThrow(matchId);
    const market = await this.ensureOutrightMarket(match.id);
    const sel = await this.prisma.marketSelection.findFirst({
      where: { id: selectionId, marketId: market.id },
    });
    if (!sel) throw appNotFound('SELECTION_NOT_FOUND');

    const nextCode = data.teamCode?.trim().toUpperCase() || sel.selectionCode;
    if (nextCode !== sel.selectionCode) {
      const dup = await this.prisma.marketSelection.findFirst({
        where: {
          marketId: market.id,
          selectionCode: nextCode,
          id: { not: selectionId },
        },
      });
      if (dup) {
        throw appBadRequest('OUTRIGHT_SELECTION_EXISTS');
      }
      await this.prisma.marketSelection.update({
        where: { id: selectionId },
        data: {
          selectionCode: nextCode,
          selectionName:
            data.teamZh?.trim() || data.teamEn?.trim() || sel.selectionName,
        },
      });
    } else if (data.teamZh?.trim() || data.teamEn?.trim()) {
      await this.prisma.marketSelection.update({
        where: { id: selectionId },
        data: {
          selectionName:
            data.teamZh?.trim() || data.teamEn?.trim() || sel.selectionName,
        },
      });
    }

    const team = await this.prisma.team.upsert({
      where: { code: nextCode },
      create: { code: nextCode },
      update: {},
    });

    if (data.teamZh !== undefined || data.teamEn !== undefined) {
      await this.upsertTeamTranslations(team.id, {
        'zh-CN': data.teamZh?.trim() || data.teamEn?.trim() || nextCode,
        'en-US': data.teamEn?.trim() || data.teamZh?.trim() || nextCode,
      });
    }

    if (data.logoUrl !== undefined) {
      const logoUrl = data.logoUrl?.trim() ? data.logoUrl.trim() : null;
      await this.prisma.team.update({
        where: { id: team.id },
        data: { logoUrl },
      });
    }

    return this.getForAdmin(matchId);
  }

  async closeSelection(matchId: bigint, selectionId: bigint) {
    const match = await this.getOutrightMatchOrThrow(matchId);
    const market = await this.ensureOutrightMarket(match.id);
    const sel = await this.prisma.marketSelection.findFirst({
      where: { id: selectionId, marketId: market.id },
    });
    if (!sel) throw appNotFound('SELECTION_NOT_FOUND');
    await this.prisma.marketSelection.update({
      where: { id: selectionId },
      data: { status: 'CLOSED' },
    });
    return this.getForAdmin(matchId);
  }

  async batchUpdateOdds(
    matchId: bigint,
    updates: Array<{ selectionId: string; odds: number }>,
    operatorId: bigint,
  ) {
    await this.getOutrightMatchOrThrow(matchId);
    const market = await this.ensureOutrightMarket(matchId);
    const allowed = new Set(
      (
        await this.prisma.marketSelection.findMany({
          where: { marketId: market.id },
          select: { id: true },
        })
      ).map((s) => s.id.toString()),
    );

    for (const u of updates) {
      if (!allowed.has(u.selectionId)) {
        throw appBadRequest('OUTRIGHT_SELECTION_INVALID');
      }
    }

    await this.markets.batchUpdateOdds(
      updates.map((u) => ({ selectionId: BigInt(u.selectionId), odds: u.odds })),
      operatorId,
    );
    return this.getForAdmin(matchId);
  }

  async importWc2026Canonical() {
    const { matchId } = await syncWc2026OutrightMarket(this.prisma, {
      forceCanonical: true,
    });
    return this.getForAdmin(matchId);
  }

  async listForPlayer(locale: string) {
    await this.trySyncWc2026();

    const matches = await this.prisma.match.findMany({
      where: {
        status: 'PUBLISHED',
        isOutright: true,
        sportType: 'FOOTBALL',
        deletedAt: null,
        league: { isActive: true, deletedAt: null },
      },
      include: {
        markets: {
          where: { marketType: OUTRIGHT_MARKET_TYPE, status: 'OPEN' },
          include: {
            selections: {
              where: { status: 'OPEN' },
              orderBy: { sortOrder: 'asc' },
            },
          },
        },
      },
      orderBy: [{ displayOrder: 'asc' }, { startTime: 'asc' }],
    });

    const results = [];
    for (const match of matches) {
      const league = await this.prisma.league.findUniqueOrThrow({
        where: { id: match.leagueId },
      });
      const leagueName = await this.getTranslation('LEAGUE', match.leagueId, locale);
      const market = match.markets[0];
      if (!market) continue;

      const selections = await Promise.all(
        market.selections
          .filter((sel) => sel.selectionCode !== PLACEHOLDER_TEAM_CODE)
          .map(async (sel) => {
            const team = await this.prisma.team.findUnique({
              where: { code: sel.selectionCode },
            });
            const translated = team
              ? await this.getTranslation('TEAM', team.id, locale)
              : '';
            const teamZh = team
              ? await this.getTranslation('TEAM', team.id, 'zh-CN')
              : sel.selectionName;
            const teamEn = team
              ? await this.getTranslation('TEAM', team.id, 'en-US')
              : sel.selectionName;
            const teamName =
              translated ||
              (locale.startsWith('zh') ? teamZh : teamEn) ||
              sel.selectionName;
            return {
              id: sel.id.toString(),
              teamCode: sel.selectionCode,
              teamName,
              logoUrl: team?.logoUrl ?? null,
              odds: sel.odds.toString(),
              oddsVersion: sel.oddsVersion.toString(),
            };
          }),
      );

      if (!selections.length) continue;

      const [titleZh, titleEn, titleMs] = await Promise.all([
        this.getOutrightTitle(match.id, 'zh-CN'),
        this.getOutrightTitle(match.id, 'en-US'),
        this.getOutrightTitle(match.id, 'ms-MY'),
      ]);
      const localizedTitle = this.pickOutrightTitleForLocale(locale, {
        zh: titleZh,
        en: titleEn,
        ms: titleMs,
      });
      const title =
        localizedTitle ||
        match.matchName?.trim() ||
        `*${leagueName || 'Outright'} ${locale.startsWith('zh') ? '冠军' : 'Winner'}`;

      results.push({
        id: match.id.toString(),
        leagueId: match.leagueId.toString(),
        leagueCode: league.code,
        leagueName: leagueName || '',
        title: title.startsWith('*') ? title : `*${title}`,
        marketId: market.id.toString(),
        selectionCount: selections.length,
        selections,
      });
    }

    return results;
  }

  /** @deprecated 使用 listForPlayer */
  async getWc2026ForPlayer(locale: string) {
    return this.listForPlayer(locale);
  }

  private async trySyncWc2026() {
    try {
      await syncWc2026OutrightMarket(this.prisma, { forceCanonical: false });
    } catch {
      /* 联赛未 seed 时忽略 */
    }
  }

  private async getOutrightMatchOrThrow(matchId: bigint) {
    const match = await this.prisma.match.findFirst({
      where: { id: matchId, isOutright: true, deletedAt: null },
    });
    if (!match) throw appNotFound('OUTRIGHT_EVENT_NOT_FOUND');
    return match;
  }

  private async ensureOutrightMarket(matchId: bigint) {
    let market = await this.prisma.market.findFirst({
      where: { matchId, marketType: OUTRIGHT_MARKET_TYPE },
    });
    if (!market) {
      market = await this.prisma.market.create({
        data: {
          matchId,
          marketType: OUTRIGHT_MARKET_TYPE,
          period: 'OUTRIGHT',
          allowSingle: true,
          allowParlay: false,
          sortOrder: 1,
          status: 'OPEN',
        },
      });
    }
    return market;
  }

  private async ensurePlaceholderTeam() {
    const existing = await this.prisma.team.findUnique({
      where: { code: PLACEHOLDER_TEAM_CODE },
    });
    if (existing) return existing;
    return this.prisma.team.create({
      data: { code: PLACEHOLDER_TEAM_CODE },
    });
  }

  private async upsertTeamTranslations(
    teamId: bigint,
    names: Record<string, string>,
  ) {
    for (const [locale, value] of Object.entries(names)) {
      if (!value) continue;
      await this.prisma.entityTranslation.upsert({
        where: {
          entityType_entityId_locale_fieldName: {
            entityType: 'TEAM',
            entityId: teamId,
            locale,
            fieldName: 'name',
          },
        },
        create: {
          entityType: 'TEAM',
          entityId: teamId,
          locale,
          fieldName: 'name',
          value,
        },
        update: { value },
      });
    }
  }

  private playerVisibility(
    matchStatus: string,
    market: { status: string } | null | undefined,
    selections: Array<{ selectionCode: string; status: string }>,
    leagueIsActive = true,
  ): { playerVisible: boolean; playerHiddenReason: string | null } {
    const openCount = selections.filter(
      (s) => s.status === 'OPEN' && s.selectionCode !== PLACEHOLDER_TEAM_CODE,
    ).length;
    return this.playerVisibilityByCounts(
      matchStatus,
      market,
      openCount,
      leagueIsActive,
    );
  }

  private playerVisibilityByCounts(
    matchStatus: string,
    market: { status: string } | null | undefined,
    openSelectionCount: number,
    leagueIsActive = true,
  ): { playerVisible: boolean; playerHiddenReason: string | null } {
    if (!leagueIsActive) {
      return { playerVisible: false, playerHiddenReason: 'LEAGUE_INACTIVE' };
    }
    if (matchStatus !== 'PUBLISHED') {
      return { playerVisible: false, playerHiddenReason: 'NOT_PUBLISHED' };
    }
    if (!market || market.status !== 'OPEN') {
      return { playerVisible: false, playerHiddenReason: 'MARKET_CLOSED' };
    }
    if (openSelectionCount === 0) {
      return { playerVisible: false, playerHiddenReason: 'NO_SELECTIONS' };
    }
    return { playerVisible: true, playerHiddenReason: null };
  }

  private async getTranslation(
    entityType: string,
    entityId: bigint,
    locale: string,
  ): Promise<string> {
    const row = await this.prisma.entityTranslation.findFirst({
      where: { entityType, entityId, locale, fieldName: 'name' },
    });
    return row?.value ?? '';
  }

  private async getOutrightTitle(matchId: bigint, locale: string): Promise<string> {
    const row = await this.prisma.entityTranslation.findFirst({
      where: {
        entityType: 'MATCH',
        entityId: matchId,
        locale,
        fieldName: 'title',
      },
    });
    return row?.value?.trim() ?? '';
  }

  private resolveOutrightMatchName(data: {
    titleZh?: string;
    titleEn?: string;
    titleMs?: string;
  }): string {
    return (
      data.titleEn?.trim() ||
      data.titleZh?.trim() ||
      data.titleMs?.trim() ||
      'Outright'
    );
  }

  private pickOutrightTitleForLocale(
    locale: string,
    titles: { zh: string; en: string; ms: string },
  ): string {
    if (locale === 'ms-MY' || locale.startsWith('ms')) {
      return titles.ms || titles.en || titles.zh;
    }
    if (locale.startsWith('zh')) {
      return titles.zh || titles.en || titles.ms;
    }
    return titles.en || titles.zh || titles.ms;
  }

  private async upsertOutrightTitles(
    matchId: bigint,
    titles: { zh?: string; en?: string; ms?: string },
  ) {
    const entries: Array<[string, string | undefined]> = [
      ['zh-CN', titles.zh],
      ['en-US', titles.en],
      ['ms-MY', titles.ms],
    ];
    for (const [locale, raw] of entries) {
      if (raw === undefined) continue;
      const value = raw.trim();
      if (!value) continue;
      await this.prisma.entityTranslation.upsert({
        where: {
          entityType_entityId_locale_fieldName: {
            entityType: 'MATCH',
            entityId: matchId,
            locale,
            fieldName: 'title',
          },
        },
        create: {
          entityType: 'MATCH',
          entityId: matchId,
          locale,
          fieldName: 'title',
          value,
        },
        update: { value },
      });
    }
  }
}
