import { beforeEach, describe, expect, it, vi } from 'vitest';

const mocked = vi.hoisted(() => ({
  postTweet: vi.fn(),
  getTweetCountLast24Hours: vi.fn(),
  expireStaleApprovedContent: vi.fn(),
  getApprovedContent: vi.fn(),
  getSocialPostCountLastHours: vi.fn(),
  updateContentStatus: vi.fn(),
  getContentThreadParts: vi.fn(),
}));

vi.mock('../../../twitter/services/twitter-api.service', () => ({
  postTweet: mocked.postTweet,
}));

vi.mock('../../../twitter/twitter-data-queries', () => ({
  getTweetCountLast24Hours: mocked.getTweetCountLast24Hours,
}));

vi.mock('../data-queries', () => ({
  expireStaleApprovedContent: mocked.expireStaleApprovedContent,
  getApprovedContent: mocked.getApprovedContent,
  getSocialPostCountLastHours: mocked.getSocialPostCountLastHours,
  updateContentStatus: mocked.updateContentStatus,
  getContentThreadParts: mocked.getContentThreadParts,
}));

describe('social-engine distributor', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    process.env.RAINMAKER_TWITTER_402_BACKOFF_HOURS = '6';
    process.env.RAINMAKER_SOCIAL_MAX_CONTENT_AGE_HOURS = '12';
    process.env.RAINMAKER_SOCIAL_MAX_POSTS_PER_HOUR = '1';
    mocked.expireStaleApprovedContent.mockResolvedValue(0);
    mocked.getSocialPostCountLastHours.mockResolvedValue(0);
  });

  it('defers approved content instead of mass-failing on CreditsDepleted', async () => {
    mocked.getTweetCountLast24Hours.mockResolvedValue(0);
    mocked.getApprovedContent.mockResolvedValue([
      { id: 'c1', text: 'first', source_data: { trend: 'a' }, sport: 'basketball', league: 'nba', content_type: 'curated_news' },
      { id: 'c2', text: 'second', source_data: { trend: 'b' }, sport: 'basketball', league: 'nba', content_type: 'curated_news' },
    ]);
    mocked.postTweet.mockRejectedValue(
      new Error('Twitter POST /2/tweets failed 402: {"title":"CreditsDepleted"}'),
    );

    const { distributeContent } = await import('../distributor');
    const posted = await distributeContent();

    expect(posted).toBe(0);
    expect(mocked.expireStaleApprovedContent).toHaveBeenCalledWith(12);
    expect(mocked.getSocialPostCountLastHours).toHaveBeenCalledWith(1);
    expect(mocked.updateContentStatus).toHaveBeenCalledTimes(2);
    expect(mocked.updateContentStatus).toHaveBeenNthCalledWith(
      1,
      'c1',
      'approved',
      expect.objectContaining({
        source_data: expect.objectContaining({
          trend: 'a',
          distribution_hold_reason: 'twitter_credits_depleted',
          last_distribution_error: 'CreditsDepleted',
        }),
      }),
    );
    expect(mocked.updateContentStatus).toHaveBeenNthCalledWith(
      2,
      'c2',
      'approved',
      expect.objectContaining({
        source_data: expect.objectContaining({
          trend: 'b',
          distribution_hold_reason: 'twitter_credits_depleted',
        }),
      }),
    );
  });

  it('blocks non-American sports content from posting', async () => {
    mocked.getTweetCountLast24Hours.mockResolvedValue(0);
    mocked.getApprovedContent.mockResolvedValue([
      {
        id: 'soccer-1',
        text: 'Premier League angle',
        source_data: { trend: 'soccer' },
        sport: 'soccer',
        league: null,
        content_type: 'curated_news',
      },
    ]);

    const { distributeContent } = await import('../distributor');
    const posted = await distributeContent();

    expect(posted).toBe(0);
    expect(mocked.getSocialPostCountLastHours).toHaveBeenCalledWith(1);
    expect(mocked.postTweet).not.toHaveBeenCalled();
    expect(mocked.updateContentStatus).toHaveBeenCalledWith(
      'soccer-1',
      'failed',
      expect.objectContaining({
        source_data: expect.objectContaining({
          trend: 'soccer',
          distribution_skip_reason: 'non_american_sport',
        }),
      }),
    );
  });

  it('marks stale approved backlog before distribution', async () => {
    mocked.expireStaleApprovedContent.mockResolvedValue(3);
    mocked.getTweetCountLast24Hours.mockResolvedValue(0);
    mocked.getApprovedContent.mockResolvedValue([]);

    const { distributeContent } = await import('../distributor');
    const posted = await distributeContent();

    expect(posted).toBe(0);
    expect(mocked.expireStaleApprovedContent).toHaveBeenCalledWith(12);
    expect(mocked.getSocialPostCountLastHours).toHaveBeenCalledWith(1);
    expect(mocked.getApprovedContent).toHaveBeenCalledWith(20, 12);
  });

  it('keeps posting valid American content even when disallowed items lead the queue', async () => {
    mocked.getTweetCountLast24Hours.mockResolvedValue(0);
    mocked.getApprovedContent.mockResolvedValue([
      {
        id: 'soccer-1',
        text: 'Premier League angle',
        source_data: { trend: 'soccer' },
        sport: 'soccer',
        league: null,
        content_type: 'curated_news',
      },
      {
        id: 'nba-1',
        text: 'Fresh NBA angle',
        source_data: { trend: 'nba' },
        sport: 'nba',
        league: null,
        content_type: 'curated_news',
        persona_slug: 'the_sharp',
      },
    ]);
    mocked.postTweet.mockResolvedValue({ tweetId: 'tweet-1' });

    const { distributeContent } = await import('../distributor');
    const posted = await distributeContent();

    expect(posted).toBe(1);
    expect(mocked.getApprovedContent).toHaveBeenCalledWith(20, 12);
    expect(mocked.postTweet).toHaveBeenCalledTimes(1);
    expect(mocked.postTweet).toHaveBeenCalledWith('Fresh NBA angle');
    expect(mocked.updateContentStatus).toHaveBeenCalledWith(
      'soccer-1',
      'failed',
      expect.objectContaining({
        source_data: expect.objectContaining({
          trend: 'soccer',
          distribution_skip_reason: 'non_american_sport',
        }),
      }),
    );
    expect(mocked.updateContentStatus).toHaveBeenCalledWith(
      'nba-1',
      'posted',
      expect.objectContaining({
        tweet_id: 'tweet-1',
      }),
    );
  });

  it('skips distribution when social-engine already posted within the last hour', async () => {
    mocked.getSocialPostCountLastHours.mockResolvedValue(1);
    mocked.getTweetCountLast24Hours.mockResolvedValue(0);

    const { distributeContent } = await import('../distributor');
    const posted = await distributeContent();

    expect(posted).toBe(0);
    expect(mocked.getSocialPostCountLastHours).toHaveBeenCalledWith(1);
    expect(mocked.getApprovedContent).not.toHaveBeenCalled();
    expect(mocked.postTweet).not.toHaveBeenCalled();
  });
});
