jest.mock('@thebet365/shared', () => ({
  isPreMatchKickoff: jest.fn(() => true),
  PARLAY_MARKET_TYPES: [],
  resolveTranslationFallback: jest.fn((translations: Map<string, string> | Record<string, string>, locale: string) => {
    const get = (key: string) =>
      translations instanceof Map ? translations.get(key) : translations[key];
    return get(locale) ?? get('zh-CN') ?? get('en-US') ?? null;
  }),
}));

import { MatchesService } from './matches.service';

describe('MatchesService publish/unpublish', () => {
  const leagueId = BigInt(1);
  const matchId = BigInt(10);

  let prisma: {
    league: { findFirst: jest.Mock; findUniqueOrThrow: jest.Mock; update: jest.Mock };
    match: { findFirst: jest.Mock; update: jest.Mock };
    entityTranslation: { findFirst: jest.Mock; upsert: jest.Mock };
    settlementBatch: { deleteMany: jest.Mock };
    marketSelection: { findMany: jest.Mock };
  };
  let outright: { syncWithLeaguePublished: jest.Mock };
  let matchBetStats: { betStatsForMatches: jest.Mock };
  let service: MatchesService;

  beforeEach(() => {
    prisma = {
      league: {
        findFirst: jest.fn(),
        findUniqueOrThrow: jest.fn(),
        update: jest.fn(),
      },
      match: {
        findFirst: jest.fn(),
        update: jest.fn(),
      },
      entityTranslation: { findFirst: jest.fn().mockResolvedValue(null), upsert: jest.fn().mockResolvedValue({}) },
      settlementBatch: { deleteMany: jest.fn().mockResolvedValue({ count: 0 }) },
      marketSelection: { findMany: jest.fn().mockResolvedValue([]) },
    };
    outright = { syncWithLeaguePublished: jest.fn().mockResolvedValue(undefined) };
    matchBetStats = { betStatsForMatches: jest.fn().mockResolvedValue(new Map()) };
    service = new MatchesService(prisma as never, outright as never, matchBetStats as never);
  });

  describe('updatePlatformLeague unpublish', () => {
    const baseLeague = { id: leagueId, code: 'EPL', isActive: true, logoUrl: null, displayOrder: 0 };

    beforeEach(() => {
      prisma.league.findFirst.mockResolvedValue(baseLeague);
      prisma.league.update.mockResolvedValue({});
      prisma.league.findUniqueOrThrow.mockResolvedValue({ ...baseLeague, isActive: false });
    });

    it('rejects unpublish when outright is settled', async () => {
      prisma.match.findFirst.mockResolvedValue({ status: 'SETTLED' });

      await expect(
        service.updatePlatformLeague(leagueId, {
          leagueEn: 'EPL',
          leagueZh: '英超',
          isActive: false,
        }),
      ).rejects.toMatchObject({
        response: expect.objectContaining({ code: 'LEAGUE_UNPUBLISH_SETTLED' }),
      });
      expect(prisma.league.update).not.toHaveBeenCalled();
    });

    it('allows unpublish when outright is not settled', async () => {
      prisma.match.findFirst.mockResolvedValue({ status: 'PUBLISHED' });

      const result = await service.updatePlatformLeague(leagueId, {
        leagueEn: 'EPL',
        leagueZh: '英超',
        isActive: false,
      });

      expect(prisma.league.update).toHaveBeenCalledWith({
        where: { id: leagueId },
        data: { isActive: false },
      });
      expect(result.isPublished).toBe(false);
    });
  });

  describe('unpublishMatch', () => {
    const baseMatch = {
      id: matchId,
      status: 'PUBLISHED',
      isOutright: false,
      deletedAt: null,
    };

    beforeEach(() => {
      prisma.match.findFirst.mockResolvedValue(baseMatch);
      prisma.match.update.mockResolvedValue({ ...baseMatch, status: 'DRAFT' });
    });

    it('unpublishes published fixture to draft', async () => {
      await service.unpublishMatch(matchId);

      expect(prisma.match.update).toHaveBeenCalledWith({
        where: { id: matchId },
        data: { status: 'DRAFT', closeTime: null },
      });
    });

    it('rejects unpublish when settled', async () => {
      prisma.match.findFirst.mockResolvedValue({ ...baseMatch, status: 'SETTLED' });

      await expect(service.unpublishMatch(matchId)).rejects.toMatchObject({
        response: expect.objectContaining({ code: 'MATCH_UNPUBLISH_FORBIDDEN' }),
      });
    });

    it('clears preview settlement batch when pending settlement', async () => {
      prisma.match.findFirst.mockResolvedValue({ ...baseMatch, status: 'PENDING_SETTLEMENT' });

      await service.unpublishMatch(matchId);

      expect(prisma.settlementBatch.deleteMany).toHaveBeenCalledWith({
        where: { matchId, status: 'PREVIEW' },
      });
    });
  });
});

describe('MatchesService getSelectionsOdds', () => {
  const selectionId = BigInt(100);
  const matchId = BigInt(10);

  let prisma: { marketSelection: { findMany: jest.Mock } };
  let service: MatchesService;

  beforeEach(() => {
    prisma = {
      marketSelection: { findMany: jest.fn() },
    };
    service = new MatchesService(
      prisma as never,
      { syncWithLeaguePublished: jest.fn() } as never,
      { betStatsForMatches: jest.fn() } as never,
    );
  });

  it('returns odds snapshot for requested selections', async () => {
    prisma.marketSelection.findMany.mockResolvedValue([
      {
        id: selectionId,
        odds: { toString: () => '1.95' },
        oddsVersion: BigInt(3),
        status: 'OPEN',
        market: {
          status: 'OPEN',
          showOnPlayer: true,
          match: { id: matchId, status: 'PUBLISHED' },
        },
      },
    ]);

    const result = await service.getSelectionsOdds([selectionId]);

    expect(prisma.marketSelection.findMany).toHaveBeenCalledWith({
      where: { id: { in: [selectionId] } },
      include: { market: { include: { match: true } } },
    });
    expect(result).toEqual([
      {
        id: '100',
        odds: '1.95',
        oddsVersion: '3',
        status: 'OPEN',
        marketStatus: 'OPEN',
        marketShowOnPlayer: true,
        matchStatus: 'PUBLISHED',
        matchId: '10',
      },
    ]);
  });

  it('returns empty array when ids not found', async () => {
    prisma.marketSelection.findMany.mockResolvedValue([]);
    const result = await service.getSelectionsOdds([BigInt(999)]);
    expect(result).toEqual([]);
  });

  it('returns empty array for empty id list', async () => {
    prisma.marketSelection.findMany.mockResolvedValue([]);
    const result = await service.getSelectionsOdds([]);
    expect(prisma.marketSelection.findMany).toHaveBeenCalledWith({
      where: { id: { in: [] } },
      include: { market: { include: { match: true } } },
    });
    expect(result).toEqual([]);
  });
});

describe('MatchesService listUpcomingPublished', () => {
  const leagueId = BigInt(1);
  const homeTeamId = BigInt(2);
  const awayTeamId = BigInt(3);
  const matchId = BigInt(10);

  let prisma: {
    match: { findMany: jest.Mock };
    entityTranslation: { findMany: jest.Mock };
  };
  let service: MatchesService;

  beforeEach(() => {
    jest.useFakeTimers();
    jest.setSystemTime(new Date('2026-06-17T12:00:00.000Z'));

    prisma = {
      match: { findMany: jest.fn() },
      entityTranslation: {
        findMany: jest.fn().mockResolvedValue([{ locale: 'zh-CN', fieldName: 'name', value: '测试' }]),
      },
    };
    service = new MatchesService(
      prisma as never,
      { syncWithLeaguePublished: jest.fn() } as never,
      { betStatsForMatches: jest.fn().mockResolvedValue(new Map()) } as never,
    );
  });

  afterEach(() => {
    jest.useRealTimers();
  });

  it('queries published matches within the next 3 days sorted by startTime', async () => {
    const now = new Date('2026-06-17T12:00:00.000Z');
    const end = new Date(now);
    end.setDate(end.getDate() + 3);

    prisma.match.findMany.mockResolvedValue([
      {
        id: matchId,
        leagueId,
        homeTeamId,
        awayTeamId,
        startTime: new Date('2026-06-18T15:00:00.000Z'),
        status: 'PUBLISHED',
        isHot: false,
        displayOrder: 0,
        matchName: null,
        stage: null,
        groupName: null,
        league: { logoUrl: null },
        homeTeam: { code: 'HME', logoUrl: null },
        awayTeam: { code: 'AWY', logoUrl: null },
        score: null,
        markets: [],
      },
    ]);

    const result = await service.listUpcomingPublished('zh-CN');

    expect(prisma.match.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({
          status: { in: ['PUBLISHED', 'CLOSED', 'PENDING_SETTLEMENT'] },
          isOutright: false,
          sportType: 'FOOTBALL',
          deletedAt: null,
          startTime: { gte: now, lte: end },
        }),
        orderBy: [{ startTime: 'asc' }, { displayOrder: 'asc' }],
        take: 50,
      }),
    );
    expect(result).toHaveLength(1);
    expect(result[0]).toEqual(
      expect.objectContaining({
        id: '10',
        startTime: '2026-06-18T15:00:00.000Z',
        isHot: false,
      }),
    );
  });

  it('respects custom limit and days options', async () => {
    prisma.match.findMany.mockResolvedValue([]);

    await service.listUpcomingPublished('en-US', { limit: 20, days: 5 });

    const now = new Date('2026-06-17T12:00:00.000Z');
    const end = new Date(now);
    end.setDate(end.getDate() + 5);

    expect(prisma.match.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({
          startTime: { gte: now, lte: end },
        }),
        take: 20,
      }),
    );
  });
});

describe('MatchesService listAdminLeagueMatches', () => {
  const leagueId = BigInt(1);

  let prisma: {
    match: { findMany: jest.Mock; count: jest.Mock };
    entityTranslation: { findFirst: jest.Mock; findMany: jest.Mock };
  };
  let matchBetStats: { betStatsForMatches: jest.Mock };
  let service: MatchesService;

  beforeEach(() => {
    prisma = {
      match: { findMany: jest.fn(), count: jest.fn() },
      entityTranslation: {
        findFirst: jest.fn().mockResolvedValue(null),
        findMany: jest.fn().mockResolvedValue([]),
      },
    };
    matchBetStats = { betStatsForMatches: jest.fn() };
    service = new MatchesService(
      prisma as never,
      { syncWithLeaguePublished: jest.fn() } as never,
      matchBetStats as never,
    );
  });

  it('filters by hasBets = true', async () => {
    const mockMatches = [
      { id: BigInt(10), startTime: new Date(), displayOrder: 0, status: 'PUBLISHED', isOutright: false, matchName: 'M1', homeTeamId: BigInt(2), awayTeamId: BigInt(3), homeTeam: { code: 'H1' }, awayTeam: { code: 'A1' } },
      { id: BigInt(11), startTime: new Date(), displayOrder: 0, status: 'PUBLISHED', isOutright: false, matchName: 'M2', homeTeamId: BigInt(4), awayTeamId: BigInt(5), homeTeam: { code: 'H2' }, awayTeam: { code: 'A2' } },
    ];
    prisma.match.findMany.mockResolvedValue(mockMatches);
    const statsMap = new Map();
    statsMap.set('10', { betCount: 5, totalStake: '500.00', pendingCount: 0 });
    statsMap.set('11', { betCount: 0, totalStake: '0.00', pendingCount: 0 });
    matchBetStats.betStatsForMatches.mockResolvedValue(statsMap);

    const result = await service.listAdminLeagueMatches(leagueId, {
      hasBets: 'true',
    });

    expect(result.items).toHaveLength(1);
    expect(result.items[0].id).toBe('10');
  });

  it('sorts by betCount', async () => {
    const mockMatches = [
      { id: BigInt(10), startTime: new Date(), displayOrder: 0, status: 'PUBLISHED', isOutright: false, matchName: 'M1', homeTeamId: BigInt(2), awayTeamId: BigInt(3), homeTeam: { code: 'H1' }, awayTeam: { code: 'A1' } },
      { id: BigInt(11), startTime: new Date(), displayOrder: 0, status: 'PUBLISHED', isOutright: false, matchName: 'M2', homeTeamId: BigInt(4), awayTeamId: BigInt(5), homeTeam: { code: 'H2' }, awayTeam: { code: 'A2' } },
    ];
    prisma.match.findMany.mockResolvedValue(mockMatches);
    const statsMap = new Map();
    statsMap.set('10', { betCount: 5, totalStake: '500.00', pendingCount: 0 });
    statsMap.set('11', { betCount: 15, totalStake: '1500.00', pendingCount: 0 });
    matchBetStats.betStatsForMatches.mockResolvedValue(statsMap);

    const result = await service.listAdminLeagueMatches(leagueId, {
      orderBy: 'betCount',
    });

    expect(result.items).toHaveLength(2);
    expect(result.items[0].id).toBe('11');
    expect(result.items[1].id).toBe('10');
  });

  it('sorts by totalStake', async () => {
    const mockMatches = [
      { id: BigInt(10), startTime: new Date(), displayOrder: 0, status: 'PUBLISHED', isOutright: false, matchName: 'M1', homeTeamId: BigInt(2), awayTeamId: BigInt(3), homeTeam: { code: 'H1' }, awayTeam: { code: 'A1' } },
      { id: BigInt(11), startTime: new Date(), displayOrder: 0, status: 'PUBLISHED', isOutright: false, matchName: 'M2', homeTeamId: BigInt(4), awayTeamId: BigInt(5), homeTeam: { code: 'H2' }, awayTeam: { code: 'A2' } },
    ];
    prisma.match.findMany.mockResolvedValue(mockMatches);
    const statsMap = new Map();
    statsMap.set('10', { betCount: 5, totalStake: '1200.00', pendingCount: 0 });
    statsMap.set('11', { betCount: 15, totalStake: '800.00', pendingCount: 0 });
    matchBetStats.betStatsForMatches.mockResolvedValue(statsMap);

    const result = await service.listAdminLeagueMatches(leagueId, {
      orderBy: 'totalStake',
    });

    expect(result.items).toHaveLength(2);
    expect(result.items[0].id).toBe('10');
    expect(result.items[1].id).toBe('11');
  });
});
