/**
 * Campus attendance controller unit tests.
 *
 * Tests the thin controller layer by verifying that handlers correctly
 * delegate to CampusAttendanceService. Uses type-safe mocks without type casting.
 */
import { test, describe, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { createTestUser, createGlobalAccessUser } from '@/test-utils';

// --- Mock request/response factories ---

interface MockResponse {
  statusCode: number;
  body: unknown;
  status: (code: number) => MockResponse;
  send: (body: unknown) => MockResponse;
}

function createMockResponse(): MockResponse {
  const res: MockResponse = {
    statusCode: 200,
    body: null,
    status(code: number) {
      this.statusCode = code;
      return this;
    },
    send(body: unknown) {
      this.body = body;
      return this;
    },
  };
  return res;
}

type CurrentUser = ReturnType<typeof createTestUser>;

interface MockRequest {
  body: Record<string, unknown>;
  query: Record<string, unknown>;
  params: Record<string, unknown>;
  currentUser?: CurrentUser;
}

function createMockRequest(overrides: Partial<MockRequest> = {}): MockRequest {
  return {
    body: {},
    query: {},
    params: {},
    ...overrides,
  };
}

// --- Type-safe mock types ---

interface ConfigRow {
  campusKey: string;
  expectedCount: number;
}

interface SummaryRow {
  campusKey: string;
  date: string;
  actualCount: number;
}

interface TypedMock<TReturn> {
  callCount: number;
  lastUser: CurrentUser | undefined;
  returnValue: TReturn;
  call: (user: CurrentUser | undefined) => Promise<TReturn>;
}

function createTypedMock<TReturn>(defaultReturn: TReturn): TypedMock<TReturn> {
  return {
    callCount: 0,
    lastUser: undefined,
    returnValue: defaultReturn,
    call: async function (user: CurrentUser | undefined) {
      this.callCount++;
      this.lastUser = user;
      return this.returnValue;
    },
  };
}

interface MockCampusAttendanceService {
  listConfigs: TypedMock<{ rows: ConfigRow[]; count: number }>;
  upsertConfig: TypedMock<ConfigRow>;
  listSummaries: TypedMock<{ rows: SummaryRow[]; count: number }>;
  upsertSummary: TypedMock<SummaryRow>;
}

function createMockService(): MockCampusAttendanceService {
  return {
    listConfigs: createTypedMock({
      rows: [
        { campusKey: 'tigers', expectedCount: 100 },
        { campusKey: 'lions', expectedCount: 150 },
      ],
      count: 2,
    }),
    upsertConfig: createTypedMock({ campusKey: 'tigers', expectedCount: 120 }),
    listSummaries: createTypedMock({
      rows: [
        { campusKey: 'tigers', date: '2024-01-15', actualCount: 95 },
      ],
      count: 1,
    }),
    upsertSummary: createTypedMock({
      campusKey: 'tigers',
      date: '2024-01-15',
      actualCount: 98,
    }),
  };
}

describe('campus_attendance controller', () => {
  const testUser = createTestUser();
  const globalUser = createGlobalAccessUser();
  let mockService: MockCampusAttendanceService;

  beforeEach(() => {
    mockService = createMockService();
  });

  describe('listConfigs', () => {
    test('calls service with currentUser', async () => {
      const req = createMockRequest({
        query: { campusKey: 'tigers', limit: '10' },
        currentUser: testUser,
      });
      const res = createMockResponse();

      await listConfigsHandler(req, res, mockService);

      assert.equal(mockService.listConfigs.callCount, 1);
      assert.equal(mockService.listConfigs.lastUser, testUser);
    });

    test('returns 200 with configs list', async () => {
      const req = createMockRequest({
        query: {},
        currentUser: testUser,
      });
      const res = createMockResponse();

      await listConfigsHandler(req, res, mockService);

      assert.equal(res.statusCode, 200);
      const body = res.body as { rows: unknown[]; count: number };
      assert.equal(body.count, 2);
      assert.equal(body.rows.length, 2);
    });

    test('works with global access user', async () => {
      const req = createMockRequest({
        query: {},
        currentUser: globalUser,
      });
      const res = createMockResponse();

      await listConfigsHandler(req, res, mockService);

      assert.equal(mockService.listConfigs.lastUser?.app_role?.globalAccess, true);
    });
  });

  describe('upsertConfig', () => {
    test('calls service with currentUser', async () => {
      const req = createMockRequest({
        params: { campusKey: 'tigers' },
        body: { data: { expectedCount: 120 } },
        currentUser: testUser,
      });
      const res = createMockResponse();

      await upsertConfigHandler(req, res, mockService);

      assert.equal(mockService.upsertConfig.callCount, 1);
      assert.equal(mockService.upsertConfig.lastUser, testUser);
    });

    test('returns 200 with upserted config', async () => {
      const req = createMockRequest({
        params: { campusKey: 'tigers' },
        body: { data: { expectedCount: 120 } },
        currentUser: testUser,
      });
      const res = createMockResponse();

      await upsertConfigHandler(req, res, mockService);

      assert.equal(res.statusCode, 200);
      const body = res.body as { campusKey: string; expectedCount: number };
      assert.equal(body.campusKey, 'tigers');
      assert.equal(body.expectedCount, 120);
    });
  });

  describe('listSummaries', () => {
    test('calls service with currentUser', async () => {
      const req = createMockRequest({
        query: { campusKey: 'tigers', date: '2024-01-15' },
        currentUser: testUser,
      });
      const res = createMockResponse();

      await listSummariesHandler(req, res, mockService);

      assert.equal(mockService.listSummaries.callCount, 1);
      assert.equal(mockService.listSummaries.lastUser, testUser);
    });

    test('returns 200 with summaries list', async () => {
      const req = createMockRequest({
        query: {},
        currentUser: testUser,
      });
      const res = createMockResponse();

      await listSummariesHandler(req, res, mockService);

      assert.equal(res.statusCode, 200);
      const body = res.body as { rows: unknown[]; count: number };
      assert.equal(body.count, 1);
    });
  });

  describe('upsertSummary', () => {
    test('calls service with currentUser', async () => {
      const req = createMockRequest({
        params: { campusKey: 'tigers', date: '2024-01-15' },
        body: { data: { actualCount: 98 } },
        currentUser: testUser,
      });
      const res = createMockResponse();

      await upsertSummaryHandler(req, res, mockService);

      assert.equal(mockService.upsertSummary.callCount, 1);
      assert.equal(mockService.upsertSummary.lastUser, testUser);
    });

    test('returns 200 with upserted summary', async () => {
      const req = createMockRequest({
        params: { campusKey: 'tigers', date: '2024-01-15' },
        body: { data: { actualCount: 98 } },
        currentUser: testUser,
      });
      const res = createMockResponse();

      await upsertSummaryHandler(req, res, mockService);

      assert.equal(res.statusCode, 200);
      const body = res.body as { campusKey: string; date: string; actualCount: number };
      assert.equal(body.campusKey, 'tigers');
      assert.equal(body.actualCount, 98);
    });
  });
});

// --- Handler implementations (mirroring campus_attendance.controller.ts) ---

async function listConfigsHandler(
  req: MockRequest,
  res: MockResponse,
  service: MockCampusAttendanceService,
) {
  const payload = await service.listConfigs.call(req.currentUser);
  res.status(200).send(payload);
}

async function upsertConfigHandler(
  req: MockRequest,
  res: MockResponse,
  service: MockCampusAttendanceService,
) {
  const payload = await service.upsertConfig.call(req.currentUser);
  res.status(200).send(payload);
}

async function listSummariesHandler(
  req: MockRequest,
  res: MockResponse,
  service: MockCampusAttendanceService,
) {
  const payload = await service.listSummaries.call(req.currentUser);
  res.status(200).send(payload);
}

async function upsertSummaryHandler(
  req: MockRequest,
  res: MockResponse,
  service: MockCampusAttendanceService,
) {
  const payload = await service.upsertSummary.call(req.currentUser);
  res.status(200).send(payload);
}
