import { auditContext } from '../audit/audit-actor';

// 판정 · 버전 검사는 이 spec 의 대상이 아니다 (쓰기 순서와 감사 원자성이 대상). 판정 본문은 permission-guardrail.spec 과
// 격리 시험 (permission-guardrail.itspec · admin-edit-guardrail.itspec) 이 실제 DB 로 본다.
jest.mock('../auth/permission/permission-guardrail', () => ({
  ...jest.requireActual('../auth/permission/permission-guardrail'),
  assertAccountGuardrailInTx: jest.fn().mockResolvedValue({ targetAthId: null }),
  assertRoleGuardrailInTx: jest.fn().mockResolvedValue(undefined),
  assertVersionMatch: jest.fn(),
}));
jest.mock('../auth/permission/permission-version', () => ({
  ...jest.requireActual('../auth/permission/permission-version'),
  bumpPermissionVersion: jest.fn().mockResolvedValue(undefined),
  lockPermissionVersion: jest.fn().mockResolvedValue(undefined),
  readGlobalVersion: jest.fn().mockResolvedValue(1),
}));
import { ForbiddenException } from '@nestjs/common';
import { AdminController } from './admin.controller';
import { AdminService } from './admin.service';

//* 가짜 리포지터리 — 생성 쓰기 호출을 기록한다. "거부했다" 는 주장은 쓰기 0회로만 증명된다.
const fakeRepo = () => ({
  create: jest.fn(async () => undefined),
});

/**
 * 가짜 판정 서비스 — 최고관리자 지정 상태(designatedSuperId)를 하나로 준다. isSuperAdmin 과
 * readDesignation 이 그 하나에서 함께 나와 어긋나지 않는다. null 이면 지정 없음(state 'none').
 */
const fakeSuper = (designatedSuperId: number | null = null) => ({
  isSuperAdmin: jest.fn(
    async (a: any) =>
      designatedSuperId !== null && Number(a?.admId) === designatedSuperId,
  ),
  readDesignation: jest.fn(async () =>
    designatedSuperId === null
      ? { state: 'none' }
      : {
          state: 'designated',
          admId: designatedSuperId,
          loginId: 'super',
          createdAt: '2026-01-01',
        },
  ),
});

const build = (designatedSuperId: number | null = null) => {
  const repo = fakeRepo();
  const superAdmin = fakeSuper(designatedSuperId);
  return {
    repo,
    superAdmin,
    service: new AdminService(repo as any, superAdmin as any),
  };
};

const CTX = auditContext({} as any);
const NEW = {
  athId: 1,
  admLoginId: 'new',
  admPassword: 'Passw0rd!',
  admName: 'n',
  admPhone: '01000000000',
  admEmail: '',
};
const codeOf = (e: unknown) => (e as ForbiddenException).getResponse() as any;

describe('관리자 신규등록 — 최고관리자 전용', () => {
  it('protect: 최고관리자가 지정된 환경에서 최고관리자가 아닌 관리자가 생성하면 E0827 로 거부하고 쓰지 않는다', async () => {
    const { repo, service } = build(1); // 최고관리자=admId 1 지정, 요청자=2
    const err = await service
      .createAdmin(NEW as any, { role: 'admin', admId: 2 }, CTX)
      .catch((e) => e);

    expect(err).toBeInstanceOf(ForbiddenException);
    //* 수정·삭제의 보호 예외(SUPER_ADMIN_PROTECTED)와 **같은 코드**다.
    expect(codeOf(err).code).toBe('E0827');
    expect(repo.create).toHaveBeenCalledTimes(0);
  });

  it('protect: 최고관리자는 생성할 수 있다', async () => {
    const { repo, service } = build(1);
    await service.createAdmin(NEW as any, { role: 'admin', admId: 1 }, CTX);
    expect(repo.create).toHaveBeenCalledWith(NEW, { role: 'admin', admId: 1 }, CTX);
  });

  it('비파괴: 최고관리자 미지정 환경은 수정·삭제와 같게 통과시킨다(단일 공용관리자 모델, #75)', async () => {
    const { repo, service } = build(null); // 지정 없음 → 누구나 생성(기존 동작 유지)
    await service.createAdmin(NEW as any, { role: 'admin', admId: 2 }, CTX);
    expect(repo.create).toHaveBeenCalledWith(NEW, { role: 'admin', admId: 2 }, CTX);
  });

  it('wiring: 생성 핸들러가 요청자 신원을 서비스에 넘긴다', async () => {
    const denied = build(1);
    const deniedCtl = new AdminController(denied.service);
    const err = await deniedCtl
      .createAdmin(NEW as any, { admin: { role: 'admin', admId: 2 } } as any)
      .catch((e) => e);
    expect(codeOf(err).code).toBe('E0827');
    expect(denied.repo.create).toHaveBeenCalledTimes(0);

    const allowed = build(1);
    const allowedCtl = new AdminController(allowed.service);
    await allowedCtl.createAdmin(
      NEW as any,
      {
        admin: { role: 'admin', admId: 1 },
      } as any,
    );
    expect(allowed.repo.create).toHaveBeenCalledTimes(1);
  });
});
