import request from 'supertest';
import { Role } from '../../../src/generated/prisma/enums.js';
import {
  E2E_SEED_CREDENTIALS,
  E2E_SEED_PASSWORD,
} from '../fixtures/e2e-roles.js';
import {
  closeE2EContext,
  createE2EContext,
  type E2EContext,
} from '../helpers/e2e-context.helper.js';
import { assertRoleMatrix } from '../helpers/e2e-matrix.helper.js';
import {
  expectErrorEnvelope,
  expectSuccessEnvelope,
  uniquePhone,
  uniqueSeedValue,
} from '../helpers/e2e-http.helper.js';
import { buildMiniAppMockPayload } from '../helpers/e2e-miniapp-auth.helper.js';

describe('AuthController (e2e)', () => {
  let ctx: E2EContext;

  beforeAll(async () => {
    ctx = await createE2EContext();
  });

  afterAll(async () => {
    await closeE2EContext(ctx);
  });

  describe('POST /auth/system-admin', () => {
    it('成功：创建系统管理员账号', async () => {
      const response = await request(ctx.app.getHttpServer())
        .post('/auth/system-admin')
        .send({
          name: uniqueSeedValue('Auth 系统管理员'),
          phone: uniquePhone(),
          password: 'Seed@1234',
          openId: uniqueSeedValue('auth-system-admin-openid'),
          systemAdminBootstrapKey: process.env.SYSTEM_ADMIN_BOOTSTRAP_KEY,
        });

      expectSuccessEnvelope(response, 201);
      expect(response.body.data.role).toBe(Role.SYSTEM_ADMIN);
    });

    it('失败：参数不合法返回 400', async () => {
      const response = await request(ctx.app.getHttpServer())
        .post('/auth/system-admin')
        .send({
          name: 'bad-system-admin',
          phone: '13800009999',
          password: '123',
          systemAdminBootstrapKey: process.env.SYSTEM_ADMIN_BOOTSTRAP_KEY,
        });

      expectErrorEnvelope(response, 400, '密码长度至少 8 位');
    });
  });

  describe('POST /auth/login', () => {
    it('成功：院内账号可使用手机号密码登录', async () => {
      const response = await request(ctx.app.getHttpServer())
        .post('/auth/login')
        .send({
          phone: E2E_SEED_CREDENTIALS[Role.DOCTOR].phone,
          password: E2E_SEED_PASSWORD,
          role: Role.DOCTOR,
          hospitalId: ctx.fixtures.hospitalAId,
        });

      expectSuccessEnvelope(response, 201);
      expect(response.body.data.accessToken).toEqual(expect.any(String));
      expect(response.body.data.actor.role).toBe(Role.DOCTOR);
    });

    it('成功：同手机号命中多个账号时先返回候选，再确认登录', async () => {
      const sharedPhone = uniquePhone();
      const firstUser = await request(ctx.app.getHttpServer())
        .post('/users')
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
        .send({
          name: uniqueSeedValue('Password 多账号医生'),
          phone: sharedPhone,
          password: 'Seed@1234',
          role: Role.DOCTOR,
          hospitalId: ctx.fixtures.hospitalAId,
          departmentId: ctx.fixtures.departmentA1Id,
          groupId: ctx.fixtures.groupA1Id,
        });
      expectSuccessEnvelope(firstUser, 201);

      const secondUser = await request(ctx.app.getHttpServer())
        .post('/users')
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
        .send({
          name: uniqueSeedValue('Password 多账号工程师'),
          phone: sharedPhone,
          password: 'Seed@1234',
          role: Role.ENGINEER,
          hospitalId: ctx.fixtures.hospitalAId,
        });
      expectSuccessEnvelope(secondUser, 201);

      const firstStage = await request(ctx.app.getHttpServer())
        .post('/auth/login')
        .send({
          phone: sharedPhone,
          password: 'Seed@1234',
        });

      expectSuccessEnvelope(firstStage, 201);
      expect(firstStage.body.data.needSelect).toBe(true);
      expect(firstStage.body.data.accounts).toHaveLength(2);

      const confirmResponse = await request(ctx.app.getHttpServer())
        .post('/auth/login/confirm')
        .send({
          loginTicket: firstStage.body.data.loginTicket,
          userId: firstUser.body.data.id,
        });

      expectSuccessEnvelope(confirmResponse, 201);
      expect(confirmResponse.body.data.user.id).toBe(firstUser.body.data.id);
    });

    it('失败：密码错误返回 401', async () => {
      const response = await request(ctx.app.getHttpServer())
        .post('/auth/login')
        .send({
          phone: E2E_SEED_CREDENTIALS[Role.DOCTOR].phone,
          password: 'Wrong@1234',
          role: Role.DOCTOR,
          hospitalId: ctx.fixtures.hospitalAId,
        });

      expectErrorEnvelope(response, 401, '手机号、密码或角色不匹配');
    });
  });

  describe('POST /auth/miniapp/b/phone-login', () => {
    it('成功：单院内账号手机号可直接登录', async () => {
      const response = await request(ctx.app.getHttpServer())
        .post('/auth/miniapp/b/phone-login')
        .send(buildMiniAppMockPayload('13800001004', 'seed-doctor-a-openid'));

      expectSuccessEnvelope(response, 201);
      expect(response.body.data.accessToken).toEqual(expect.any(String));
      expect(response.body.data.actor.role).toBe(Role.DOCTOR);
    });

    it('成功：同手机号多账号时先返回候选，再确认登录', async () => {
      const sharedPhone = uniquePhone();
      const firstUser = await request(ctx.app.getHttpServer())
        .post('/users')
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
        .send({
          name: uniqueSeedValue('MiniApp 多账号医生'),
          phone: sharedPhone,
          role: Role.DOCTOR,
          hospitalId: ctx.fixtures.hospitalAId,
          departmentId: ctx.fixtures.departmentA1Id,
          groupId: ctx.fixtures.groupA1Id,
        });
      expectSuccessEnvelope(firstUser, 201);

      const secondUser = await request(ctx.app.getHttpServer())
        .post('/users')
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
        .send({
          name: uniqueSeedValue('MiniApp 多账号工程师'),
          phone: sharedPhone,
          role: Role.ENGINEER,
          hospitalId: ctx.fixtures.hospitalAId,
        });
      expectSuccessEnvelope(secondUser, 201);

      const firstStage = await request(ctx.app.getHttpServer())
        .post('/auth/miniapp/b/phone-login')
        .send(
          buildMiniAppMockPayload(sharedPhone, uniqueSeedValue('multi-openid')),
        );

      expectSuccessEnvelope(firstStage, 201);
      expect(firstStage.body.data.needSelect).toBe(true);
      expect(firstStage.body.data.accounts).toHaveLength(2);

      const confirmResponse = await request(ctx.app.getHttpServer())
        .post('/auth/miniapp/b/phone-login/confirm')
        .send({
          loginTicket: firstStage.body.data.loginTicket,
          userId: firstUser.body.data.id,
        });

      expectSuccessEnvelope(confirmResponse, 201);
      expect(confirmResponse.body.data.user.id).toBe(firstUser.body.data.id);
    });

    it('成功：同一微信 openId 可切换绑定多个院内账号', async () => {
      const sharedPhone = uniquePhone();
      const sharedOpenId = uniqueSeedValue('shared-openid');
      const firstUser = await request(ctx.app.getHttpServer())
        .post('/users')
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
        .send({
          name: uniqueSeedValue('MiniApp 切换账号医生'),
          phone: sharedPhone,
          role: Role.DOCTOR,
          hospitalId: ctx.fixtures.hospitalAId,
          departmentId: ctx.fixtures.departmentA1Id,
          groupId: ctx.fixtures.groupA1Id,
        });
      expectSuccessEnvelope(firstUser, 201);

      const secondUser = await request(ctx.app.getHttpServer())
        .post('/users')
        .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
        .send({
          name: uniqueSeedValue('MiniApp 切换账号工程师'),
          phone: sharedPhone,
          role: Role.ENGINEER,
          hospitalId: ctx.fixtures.hospitalAId,
        });
      expectSuccessEnvelope(secondUser, 201);

      const firstStageForDoctor = await request(ctx.app.getHttpServer())
        .post('/auth/miniapp/b/phone-login')
        .send(buildMiniAppMockPayload(sharedPhone, sharedOpenId));
      expectSuccessEnvelope(firstStageForDoctor, 201);

      const doctorConfirm = await request(ctx.app.getHttpServer())
        .post('/auth/miniapp/b/phone-login/confirm')
        .send({
          loginTicket: firstStageForDoctor.body.data.loginTicket,
          userId: firstUser.body.data.id,
        });
      expectSuccessEnvelope(doctorConfirm, 201);

      const firstStageForEngineer = await request(ctx.app.getHttpServer())
        .post('/auth/miniapp/b/phone-login')
        .send(buildMiniAppMockPayload(sharedPhone, sharedOpenId));
      expectSuccessEnvelope(firstStageForEngineer, 201);

      const engineerConfirm = await request(ctx.app.getHttpServer())
        .post('/auth/miniapp/b/phone-login/confirm')
        .send({
          loginTicket: firstStageForEngineer.body.data.loginTicket,
          userId: secondUser.body.data.id,
        });
      expectSuccessEnvelope(engineerConfirm, 201);

      const users = await ctx.prisma.user.findMany({
        where: {
          id: {
            in: [firstUser.body.data.id, secondUser.body.data.id],
          },
        },
        select: {
          id: true,
          openId: true,
        },
        orderBy: { id: 'asc' },
      });

      expect(users).toEqual([
        { id: firstUser.body.data.id, openId: sharedOpenId },
        { id: secondUser.body.data.id, openId: sharedOpenId },
      ]);
    });

    it('失败：手机号未匹配院内账号返回 404', async () => {
      const response = await request(ctx.app.getHttpServer())
        .post('/auth/miniapp/b/phone-login')
        .send(
          buildMiniAppMockPayload(
            uniquePhone(),
            uniqueSeedValue('no-user-openid'),
          ),
        );

      expectErrorEnvelope(response, 404, '手机号未匹配到院内账号');
    });
  });

  describe('POST /auth/miniapp/c/phone-login', () => {
    it('成功：手机号唯一关联患者时可创建 C 端账号并返回 token', async () => {
      const response = await request(ctx.app.getHttpServer())
        .post('/auth/miniapp/c/phone-login')
        .send(buildMiniAppMockPayload('13800002002', 'seed-family-a2-openid'));

      expectSuccessEnvelope(response, 201);
      expect(response.body.data.accessToken).toEqual(expect.any(String));
      expect(response.body.data.familyAccount.phone).toBe('13800002002');
    });

    it('失败：手机号未关联患者档案返回 404', async () => {
      const response = await request(ctx.app.getHttpServer())
        .post('/auth/miniapp/c/phone-login')
        .send(
          buildMiniAppMockPayload(
            uniquePhone(),
            uniqueSeedValue('family-openid'),
          ),
        );

      expectErrorEnvelope(response, 404, '当前手机号未关联患者档案');
    });

    it('失败：手机号关联多份患者档案返回 409', async () => {
      const response = await request(ctx.app.getHttpServer())
        .post('/auth/miniapp/c/phone-login')
        .send(buildMiniAppMockPayload('13800002001', 'seed-family-a1-openid'));

      expectErrorEnvelope(
        response,
        409,
        '当前手机号关联了多份患者档案，请联系管理员处理',
      );
    });
  });

  describe('GET /auth/me', () => {
    it('成功：已登录用户可读取当前信息', async () => {
      const response = await request(ctx.app.getHttpServer())
        .get('/auth/me')
        .set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`);

      expectSuccessEnvelope(response, 200);
      expect(response.body.data.role).toBe(Role.DOCTOR);
    });

    it('失败：未登录返回 401', async () => {
      const response = await request(ctx.app.getHttpServer()).get('/auth/me');
      expectErrorEnvelope(response, 401, '缺少 Bearer Token');
    });

    it('角色矩阵：6 角色都可访问，未登录 401', async () => {
      await assertRoleMatrix({
        name: 'GET /auth/me role matrix',
        tokens: ctx.tokens,
        expectedStatusByRole: {
          [Role.SYSTEM_ADMIN]: 200,
          [Role.HOSPITAL_ADMIN]: 200,
          [Role.DIRECTOR]: 200,
          [Role.LEADER]: 200,
          [Role.DOCTOR]: 200,
          [Role.ENGINEER]: 200,
        },
        sendAsRole: async (_role, token) =>
          request(ctx.app.getHttpServer())
            .get('/auth/me')
            .set('Authorization', `Bearer ${token}`),
        sendWithoutToken: async () =>
          request(ctx.app.getHttpServer()).get('/auth/me'),
      });
    });
  });
});
