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

const { mockRedisGet, mockRedisSetEx } = vi.hoisted(() => ({
  mockRedisGet: vi.fn(),
  mockRedisSetEx: vi.fn(),
}));

vi.mock('../redis.js', () => ({
  default: {
    get: mockRedisGet,
    setEx: mockRedisSetEx,
  },
}));

const mockGetCredential = vi.hoisted(() => vi.fn());
vi.mock('./credential-store.js', () => ({ getCredential: mockGetCredential }));

global.fetch = vi.fn();

import { getAllPlatformHealth } from './platform-health.js';

describe('getAllPlatformHealth', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockRedisGet.mockResolvedValue(null);
    mockRedisSetEx.mockResolvedValue('OK');
  });

  it('returns cached status without hitting API', async () => {
    mockRedisGet.mockImplementation((key: string) =>
      key.includes('linkedin') ? Promise.resolve('healthy') : Promise.resolve(null)
    );
    mockGetCredential.mockResolvedValue(null);

    const results = await getAllPlatformHealth('cust1');
    const linkedin = results.find(r => r.platform === 'linkedin');
    expect(linkedin?.status).toBe('healthy');
    expect(fetch).not.toHaveBeenCalled();
  });

  it('returns disconnected when no credential stored', async () => {
    mockGetCredential.mockResolvedValue(null);
    // Second redis.get (raw key check) also returns null
    mockRedisGet.mockResolvedValue(null);

    const results = await getAllPlatformHealth('cust1');
    results.forEach(r => {
      expect(r.status).toBe('disconnected');
    });
  });

  it('returns healthy when OAuth probe succeeds', async () => {
    mockGetCredential.mockImplementation((id: string, platform: string) =>
      platform === 'linkedin' ? Promise.resolve({ accessToken: 'tok' }) : Promise.resolve(null)
    );
    (fetch as ReturnType<typeof vi.fn>).mockResolvedValue({ ok: true });
    mockRedisGet.mockImplementation((key: string) =>
      key.startsWith('creds:') ? Promise.resolve(null) : Promise.resolve(null)
    );

    const results = await getAllPlatformHealth('cust1');
    const linkedin = results.find(r => r.platform === 'linkedin');
    expect(linkedin?.status).toBe('healthy');
  });

  it('returns expired when OAuth probe returns non-ok', async () => {
    mockGetCredential.mockImplementation((_id: string, platform: string) =>
      platform === 'twitter' ? Promise.resolve({ accessToken: 'tok' }) : Promise.resolve(null)
    );
    (fetch as ReturnType<typeof vi.fn>).mockResolvedValue({ ok: false, status: 401 });
    mockRedisGet.mockResolvedValue(null);

    const results = await getAllPlatformHealth('cust1');
    const twitter = results.find(r => r.platform === 'twitter');
    expect(twitter?.status).toBe('expired');
  });

  it('returns unknown when fetch throws', async () => {
    mockGetCredential.mockImplementation((_id: string, platform: string) =>
      platform === 'tiktok' ? Promise.resolve({ accessToken: 'tok' }) : Promise.resolve(null)
    );
    (fetch as ReturnType<typeof vi.fn>).mockRejectedValue(new Error('network error'));
    mockRedisGet.mockResolvedValue(null);

    const results = await getAllPlatformHealth('cust1');
    const tiktok = results.find(r => r.platform === 'tiktok');
    expect(tiktok?.status).toBe('unknown');
  });

  it('caches the result in Redis', async () => {
    mockGetCredential.mockResolvedValue(null);
    mockRedisGet.mockResolvedValue(null);

    await getAllPlatformHealth('cust1');
    expect(mockRedisSetEx).toHaveBeenCalledWith(
      expect.stringContaining('health:cust1:'),
      600,
      expect.any(String)
    );
  });

  it('returns healthy for non-OAuth platforms when credential exists', async () => {
    mockGetCredential.mockImplementation((_id: string, platform: string) =>
      platform === 'email' ? Promise.resolve({ host: 'smtp.example.com', port: 587, user: 'u', password: 'p' }) : Promise.resolve(null)
    );
    mockRedisGet.mockResolvedValue(null);

    const results = await getAllPlatformHealth('cust1');
    const email = results.find(r => r.platform === 'email');
    expect(email?.status).toBe('healthy');
    expect(fetch).not.toHaveBeenCalled();
  });
});
