import { spawn } from 'node:child_process';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import ffmpegPath from 'ffmpeg-static';
import sharp from 'sharp';
import request from 'supertest';
import { Role } from '../../../src/generated/prisma/enums.js';
import {
  closeE2EContext,
  createE2EContext,
  type E2EContext,
} from '../helpers/e2e-context.helper.js';
import { assertRoleMatrix } from '../helpers/e2e-matrix.helper.js';
import {
  expectErrorEnvelope,
  expectSuccessEnvelope,
} from '../helpers/e2e-http.helper.js';

describe('BUploadsController (e2e)', () => {
  let ctx: E2EContext;
  let tempDir = '';
  let sampleVideoPath = '';
  let samplePngBuffer: Buffer;

  beforeAll(async () => {
    ctx = await createE2EContext();
    tempDir = await mkdtemp(join(tmpdir(), 'upload-assets-e2e-'));
    sampleVideoPath = join(tempDir, 'ct-video.mov');
    await createSampleVideo(sampleVideoPath);
    samplePngBuffer = await sharp({
      create: {
        width: 32,
        height: 32,
        channels: 3,
        background: { r: 24, g: 46, b: 82 },
      },
    })
      .png()
      .toBuffer();
  });

  afterAll(async () => {
    await closeE2EContext(ctx);
    await rm(tempDir, { recursive: true, force: true });
  });

  it('成功：HOSPITAL_ADMIN 可上传图片', async () => {
    const response = await request(ctx.app.getHttpServer())
      .post('/b/uploads')
      .set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
      .attach('file', samplePngBuffer, {
        filename: 'ct-image.png',
        contentType: 'image/png',
      });

    expectSuccessEnvelope(response, 201);
    expect(response.body.data.type).toBe('IMAGE');
    expect(response.body.data.mimeType).toBe('image/webp');
    expect(response.body.data.fileName).toMatch(/^\d{14}-ct-image\.webp$/);
    expect(response.body.data.storagePath).toMatch(
      /^\d{4}\/\d{2}\/\d{2}\/\d{14}-ct-image\.webp$/,
    );
    expect(response.body.data.url).toMatch(
      /^\/uploads\/\d{4}\/\d{2}\/\d{2}\/\d{14}-ct-image\.webp$/,
    );
    expect(response.body.data.hospital.id).toBe(ctx.fixtures.hospitalAId);
  });

  it('成功：HOSPITAL_ADMIN 可上传视频并转为 mp4', async () => {
    const response = await request(ctx.app.getHttpServer())
      .post('/b/uploads')
      .set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
      .attach('file', sampleVideoPath, {
        filename: 'ct-video.mov',
        contentType: 'video/quicktime',
      });

    expectSuccessEnvelope(response, 201);
    expect(response.body.data.type).toBe('VIDEO');
    expect(response.body.data.mimeType).toBe('video/mp4');
    expect(response.body.data.fileName).toMatch(/^\d{14}-ct-video\.mp4$/);
    expect(response.body.data.storagePath).toMatch(
      /^\d{4}\/\d{2}\/\d{2}\/\d{14}-ct-video\.mp4$/,
    );
    expect(response.body.data.url).toMatch(
      /^\/uploads\/\d{4}\/\d{2}\/\d{2}\/\d{14}-ct-video\.mp4$/,
    );
  });

  it('失败：SYSTEM_ADMIN 上传文件时未指定医院返回 400', async () => {
    const response = await request(ctx.app.getHttpServer())
      .post('/b/uploads')
      .set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
      .attach('file', samplePngBuffer, {
        filename: 'ct-image.png',
        contentType: 'image/png',
      });

    expectErrorEnvelope(
      response,
      400,
      '系统管理员上传文件时必须显式指定 hospitalId',
    );
  });

  it('失败：DIRECTOR 查询影像库返回 403', async () => {
    await request(ctx.app.getHttpServer())
      .post('/b/uploads')
      .set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
      .attach('file', samplePngBuffer, {
        filename: 'seed-image.png',
        contentType: 'image/png',
      });

    const response = await request(ctx.app.getHttpServer())
      .get('/b/uploads')
      .set('Authorization', `Bearer ${ctx.tokens[Role.DIRECTOR]}`);

    expectErrorEnvelope(response, 403, '无权限执行当前操作');
  });

  it('角色矩阵：上传允许系统/医院/主任/组长/医生/工程师，未登录 401', async () => {
    await assertRoleMatrix({
      name: 'POST /b/uploads role matrix',
      tokens: ctx.tokens,
      expectedStatusByRole: {
        [Role.SYSTEM_ADMIN]: 400,
        [Role.HOSPITAL_ADMIN]: 201,
        [Role.DIRECTOR]: 201,
        [Role.LEADER]: 201,
        [Role.DOCTOR]: 201,
        [Role.ENGINEER]: 201,
      },
      sendAsRole: async (role, token) => {
        const req = request(ctx.app.getHttpServer())
          .post('/b/uploads')
          .set('Authorization', `Bearer ${token}`)
          .attach('file', samplePngBuffer, {
            filename: `${role.toLowerCase()}.png`,
            contentType: 'image/png',
          });

        if (role === Role.SYSTEM_ADMIN) {
          return req;
        }
        return req;
      },
      sendWithoutToken: async () =>
        request(ctx.app.getHttpServer())
          .post('/b/uploads')
          .attach('file', samplePngBuffer, {
            filename: 'anonymous.png',
            contentType: 'image/png',
          }),
    });
  });

  it('角色矩阵：上传列表仅系统/医院管理员可访问，其他角色 403，未登录 401', async () => {
    await assertRoleMatrix({
      name: 'GET /b/uploads role matrix',
      tokens: ctx.tokens,
      expectedStatusByRole: {
        [Role.SYSTEM_ADMIN]: 200,
        [Role.HOSPITAL_ADMIN]: 200,
        [Role.DIRECTOR]: 403,
        [Role.LEADER]: 403,
        [Role.DOCTOR]: 403,
        [Role.ENGINEER]: 403,
      },
      sendAsRole: async (role, token) => {
        const req = request(ctx.app.getHttpServer())
          .get('/b/uploads')
          .set('Authorization', `Bearer ${token}`);
        if (role === Role.SYSTEM_ADMIN) {
          req.query({ hospitalId: ctx.fixtures.hospitalAId });
        }
        return req;
      },
      sendWithoutToken: async () =>
        request(ctx.app.getHttpServer()).get('/b/uploads'),
    });
  });
});

async function createSampleVideo(outputPath: string) {
  const command = ffmpegPath as unknown as string | null;
  if (!command) {
    throw new Error('ffmpeg-static not available');
  }

  const args = [
    '-y',
    '-f',
    'lavfi',
    '-i',
    'color=c=black:s=320x240:d=1',
    '-f',
    'lavfi',
    '-i',
    'anullsrc=r=44100:cl=stereo',
    '-shortest',
    '-c:v',
    'libx264',
    '-pix_fmt',
    'yuv420p',
    '-c:a',
    'aac',
    '-movflags',
    '+faststart',
    outputPath,
  ];

  await new Promise<void>((resolve, reject) => {
    const child = spawn(command, args);
    let stderr = '';

    child.stderr?.on('data', (chunk) => {
      stderr += String(chunk);
    });
    child.on('error', reject);
    child.on('close', (code) => {
      if (code === 0) {
        resolve();
        return;
      }
      reject(new Error(stderr.trim() || 'failed to create sample video'));
    });
  });
}
