const request = require('supertest');

jest.mock('../src/lib/prisma', () => require('./__mocks__/prisma'));
// Always miss the cache in unit tests so each test exercises the real route logic.
jest.mock('../src/lib/cache', () => ({ get: jest.fn(() => undefined), set: jest.fn(), del: jest.fn(), delByPrefix: jest.fn() }));

const prisma = require('../src/lib/prisma');
const app    = require('../src/app');

const EXP_ID = 'aaaaaaaa-0000-0000-0000-000000000001';

const EXPERIMENT = {
  id: EXP_ID,
  title: 'DS Test Study',
  created_at: new Date('2024-01-01').toISOString(),
};

function makeStatus(overrides = {}) {
  return {
    id:                     overrides.id    ?? 'dddddddd-0000-0000-0000-000000000001',
    animal_id:              overrides.animalId ?? 'bbbbbbbb-0000-0000-0000-000000000001',
    date:                   new Date(overrides.date ?? '2026-04-01'),
    analysis_summary:       overrides.analysis_summary ?? null,
    experiment_description: overrides.experiment_description ?? null,
    vitals:                 overrides.vitals  ?? null,
    treatment:              overrides.treatment ?? null,
    notes:                  overrides.notes  ?? null,
    custom_fields:          overrides.custom_fields ?? {},
  };
}

beforeEach(() => {
  jest.clearAllMocks();
});

// ── GET /api/experiments/:id/daily-statuses ────────────────────────────────────

describe('GET /api/experiments/:id/daily-statuses', () => {
  it('returns 404 when experiment does not exist', async () => {
    prisma.experiment.findUnique.mockResolvedValue(null);
    const res = await request(app).get(`/api/experiments/${EXP_ID}/daily-statuses`);
    expect(res.status).toBe(404);
    expect(res.body.error).toMatch(/not found/i);
  });

  it('returns all statuses when no filters are provided', async () => {
    prisma.experiment.findUnique.mockResolvedValue(EXPERIMENT);
    const statuses = [
      makeStatus({ id: 'id-1', analysis_summary: { total: 10, counts: { Success: 8 }, success_rate: 0.8 } }),
      makeStatus({ id: 'id-2', analysis_summary: null }),
      makeStatus({ id: 'id-3', analysis_summary: null }),
    ];
    prisma.dailyStatus.findMany.mockResolvedValue(statuses);

    const res = await request(app).get(`/api/experiments/${EXP_ID}/daily-statuses`);
    expect(res.status).toBe(200);
    expect(res.body).toHaveLength(3);
  });

  // ── analysisOnly filter — this is the scenario that caused a 500 ──────────

  it('analysisOnly=true returns only statuses with non-null analysis_summary', async () => {
    prisma.experiment.findUnique.mockResolvedValue(EXPERIMENT);
    const summary = { total: 20, counts: { Success: 15, Failure: 5 }, success_rate: 0.75 };
    prisma.dailyStatus.findMany.mockResolvedValue([
      makeStatus({ id: 'id-1', analysis_summary: summary }),
      makeStatus({ id: 'id-2', analysis_summary: null }),   // must be excluded
      makeStatus({ id: 'id-3', analysis_summary: null }),   // must be excluded
      makeStatus({ id: 'id-4', analysis_summary: summary }),
    ]);

    const res = await request(app)
      .get(`/api/experiments/${EXP_ID}/daily-statuses?analysisOnly=true`);

    expect(res.status).toBe(200);
    expect(res.body).toHaveLength(2);
    expect(res.body.every((s) => s.analysis_summary !== null)).toBe(true);
    expect(res.body.map((s) => s.id)).toEqual(['id-1', 'id-4']);
  });

  it('analysisOnly=true returns empty array when no statuses have analysis_summary', async () => {
    prisma.experiment.findUnique.mockResolvedValue(EXPERIMENT);
    prisma.dailyStatus.findMany.mockResolvedValue([
      makeStatus({ id: 'id-1', analysis_summary: null }),
      makeStatus({ id: 'id-2', analysis_summary: null }),
    ]);

    const res = await request(app)
      .get(`/api/experiments/${EXP_ID}/daily-statuses?analysisOnly=true`);

    expect(res.status).toBe(200);
    expect(res.body).toEqual([]);
  });

  it('analysisOnly=true does not 500 when Prisma returns mixed null / object values', async () => {
    // This is the exact shape that caused the production 500 — the fix must not
    // rely on a Prisma WHERE clause (which errors on Json? null filtering in v5).
    prisma.experiment.findUnique.mockResolvedValue(EXPERIMENT);
    prisma.dailyStatus.findMany.mockResolvedValue([
      makeStatus({ id: 'id-null',  analysis_summary: null }),
      makeStatus({ id: 'id-value', analysis_summary: { total: 5, counts: {}, success_rate: null } }),
    ]);

    const res = await request(app)
      .get(`/api/experiments/${EXP_ID}/daily-statuses?analysisOnly=true`);

    expect(res.status).toBe(200);
    expect(res.body).toHaveLength(1);
    expect(res.body[0].id).toBe('id-value');
  });

  it('analysisOnly=false (explicit) returns all statuses', async () => {
    prisma.experiment.findUnique.mockResolvedValue(EXPERIMENT);
    const statuses = [
      makeStatus({ id: 'id-1', analysis_summary: { total: 1, counts: {}, success_rate: null } }),
      makeStatus({ id: 'id-2', analysis_summary: null }),
    ];
    prisma.dailyStatus.findMany.mockResolvedValue(statuses);

    const res = await request(app)
      .get(`/api/experiments/${EXP_ID}/daily-statuses?analysisOnly=false`);

    expect(res.status).toBe(200);
    expect(res.body).toHaveLength(2);
  });

  // ── date filter ────────────────────────────────────────────────────────────

  it('passes date range to Prisma when ?date= is provided', async () => {
    prisma.experiment.findUnique.mockResolvedValue(EXPERIMENT);
    prisma.dailyStatus.findMany.mockResolvedValue([]);

    await request(app)
      .get(`/api/experiments/${EXP_ID}/daily-statuses?date=2026-04-23`);

    const whereArg = prisma.dailyStatus.findMany.mock.calls[0][0].where;
    expect(whereArg.date).toBeDefined();
    expect(whereArg.date.gte).toEqual(new Date('2026-04-23T00:00:00.000Z'));
  });

  it('returns 200 with empty array when no statuses exist', async () => {
    prisma.experiment.findUnique.mockResolvedValue(EXPERIMENT);
    prisma.dailyStatus.findMany.mockResolvedValue([]);

    const res = await request(app).get(`/api/experiments/${EXP_ID}/daily-statuses`);
    expect(res.status).toBe(200);
    expect(res.body).toEqual([]);
  });

  it('returned records include the expected fields', async () => {
    prisma.experiment.findUnique.mockResolvedValue(EXPERIMENT);
    const summary = { total: 3, counts: { Success: 2 }, success_rate: 0.67 };
    prisma.dailyStatus.findMany.mockResolvedValue([
      makeStatus({ id: 'id-1', vitals: '120/80', analysis_summary: summary }),
    ]);

    const res = await request(app).get(`/api/experiments/${EXP_ID}/daily-statuses`);
    expect(res.status).toBe(200);
    const rec = res.body[0];
    expect(rec).toMatchObject({
      id:               'id-1',
      vitals:           '120/80',
      analysis_summary: summary,
    });
  });
});
