import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import request from 'supertest';
import { AppModule } from '../src/app.module';

describe('H0-01 Apple Login Mock Fallback', () => {
  const OLD_ENV = { ...process.env };

  afterAll(() => {
    process.env = OLD_ENV;
  });

  describe('Dev mode without APPLE_BUNDLE_ID → mock fallback', () => {
    let app: INestApplication;

    beforeAll(async () => {
      process.env.NODE_ENV = 'development';
      process.env.JWT_SECRET = 'test-jwt-secret-for-h0-tests';
      delete process.env.APPLE_BUNDLE_ID;
      const m: TestingModule = await Test.createTestingModule({ imports: [AppModule] }).compile();
      app = m.createNestApplication();
      app.setGlobalPrefix('api', { exclude: ['admin-api/(.*)', 'internal/(.*)'] });
      await app.init();
    });

    afterAll(async () => { await app.close(); });

    it('POST /api/auth/apple → 200 with mock fallback (token >= 4 chars)', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/auth/apple')
        .send({ identityToken: 'test-apple-id-token-valid' });
      expect(res.body.success).toBe(true);
      expect(res.body.data).toHaveProperty('accessToken');
      expect(res.body.data).toHaveProperty('refreshToken');
    });

    it('POST /api/auth/apple short token → 401', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/auth/apple')
        .send({ identityToken: 'ab' });
      expect(res.body.success).toBe(false);
      expect(res.body.statusCode).toBe(401);
    });
  });

  describe('Production mode without APPLE_BUNDLE_ID → reject', () => {
    let app: INestApplication;

    beforeAll(async () => {
      process.env.NODE_ENV = 'production';
      process.env.JWT_SECRET = 'prod-test-jwt-secret-for-h0';
      process.env.ADMIN_JWT_ACCESS_SECRET = 'prod-test-admin-jwt-secret';
      delete process.env.APPLE_BUNDLE_ID;
      const m: TestingModule = await Test.createTestingModule({ imports: [AppModule] }).compile();
      app = m.createNestApplication();
      app.setGlobalPrefix('api', { exclude: ['admin-api/(.*)', 'internal/(.*)'] });
      await app.init();
    });

    afterAll(async () => { await app.close(); });

    it('POST /api/auth/apple → 401 (production without Apple config)', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/auth/apple')
        .send({ identityToken: 'valid-looking-apple-identity-token' });
      expect(res.body.success).toBe(false);
      expect(res.body.statusCode).toBe(401);
      expect(res.body.message).toContain('未配置');
    });
  });

  describe('Production mode with APPLE_BUNDLE_ID → real verification', () => {
    let app: INestApplication;

    beforeAll(async () => {
      process.env.NODE_ENV = 'production';
      process.env.JWT_SECRET = 'prod-test-jwt-secret-for-h0';
      process.env.ADMIN_JWT_ACCESS_SECRET = 'prod-test-admin-jwt-secret';
      process.env.APPLE_BUNDLE_ID = 'com.test.bundle';
      const m: TestingModule = await Test.createTestingModule({ imports: [AppModule] }).compile();
      app = m.createNestApplication();
      app.setGlobalPrefix('api', { exclude: ['admin-api/(.*)', 'internal/(.*)'] });
      await app.init();
    });

    afterAll(async () => { await app.close(); });

    it('POST /api/auth/apple → 200 (jose mocked, valid response)', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/auth/apple')
        .send({ identityToken: 'any-jwt' });
      expect(res.body.success).toBe(true);
      expect(res.body.data).toHaveProperty('accessToken');
    });
  });
});

describe('H0-02 InternalAuthGuard', () => {
  const OLD_ENV = { ...process.env };
  const INTERNAL_KEY = 'test-internal-api-key-h0';

  afterAll(() => {
    process.env = OLD_ENV;
  });

  describe('Without internal API key → 401', () => {
    let app: INestApplication;

    beforeAll(async () => {
      process.env.NODE_ENV = 'development';
      process.env.JWT_SECRET = 'test-jwt-h0-02';
      process.env.INTERNAL_API_KEY = INTERNAL_KEY;
      const m: TestingModule = await Test.createTestingModule({ imports: [AppModule] }).compile();
      app = m.createNestApplication();
      app.setGlobalPrefix('api', { exclude: ['admin-api/(.*)', 'internal/(.*)'] });
      await app.init();
    });

    afterAll(async () => { await app.close(); });

    it('GET /internal/rag/jobs/next without key → 401', async () => {
      const res = await request(app.getHttpServer())
        .get('/internal/rag/jobs/next');
      expect(res.body.success).toBe(false);
      expect(res.body.statusCode).toBe(401);
    });

    it('GET /internal/rag/jobs/:id without key → 401', async () => {
      const res = await request(app.getHttpServer())
        .get('/internal/rag/jobs/test-id');
      expect(res.body.success).toBe(false);
      expect(res.body.statusCode).toBe(401);
    });

    it('POST /internal/rag/chunks without key → 401', async () => {
      const res = await request(app.getHttpServer())
        .post('/internal/rag/chunks')
        .send({ chunks: [] });
      expect(res.body.success).toBe(false);
      expect(res.body.statusCode).toBe(401);
    });
  });

  describe('With valid internal API key → accessible', () => {
    let app: INestApplication;

    beforeAll(async () => {
      process.env.NODE_ENV = 'development';
      process.env.JWT_SECRET = 'test-jwt-h0-02';
      process.env.INTERNAL_API_KEY = INTERNAL_KEY;
      const m: TestingModule = await Test.createTestingModule({ imports: [AppModule] }).compile();
      app = m.createNestApplication();
      app.setGlobalPrefix('api', { exclude: ['admin-api/(.*)', 'internal/(.*)'] });
      await app.init();
    });

    afterAll(async () => { await app.close(); });

    it('GET /internal/rag/jobs/next with valid key → 200', async () => {
      const res = await request(app.getHttpServer())
        .get('/internal/rag/jobs/next')
        .set('X-Internal-API-Key', INTERNAL_KEY);
      expect(res.body.success).toBe(true);
    });

    it('POST /internal/rag/chunks with valid key → 200', async () => {
      const res = await request(app.getHttpServer())
        .post('/internal/rag/chunks')
        .set('X-Internal-API-Key', INTERNAL_KEY)
        .send({ chunks: [] });
      expect(res.body.success).toBe(true);
    });

    it('POST /internal/rag/jobs/:id/claim with valid key → 200', async () => {
      const res = await request(app.getHttpServer())
        .post('/internal/rag/jobs/test-id/claim')
        .set('X-Internal-API-Key', INTERNAL_KEY)
        .send({ workerId: 'test-worker' });
      expect(res.body.success).toBe(true);
    });
  });
});

describe('H0-03 JwtAuthGuard user status check', () => {
  const OLD_ENV = { ...process.env };

  afterAll(() => {
    process.env = OLD_ENV;
  });

  let app: INestApplication;
  let jwtService: any;

  beforeAll(async () => {
    process.env.NODE_ENV = 'development';
    process.env.JWT_SECRET = 'test-jwt-h0-03';
    const m: TestingModule = await Test.createTestingModule({ imports: [AppModule] }).compile();
    app = m.createNestApplication();
    app.setGlobalPrefix('api', { exclude: ['admin-api/(.*)', 'internal/(.*)'] });
    await app.init();
    jwtService = app.get(JwtService);
  });

  afterAll(async () => { await app.close(); });

  it('active user → 200 accessing /api/*', async () => {
    const token = await jwtService.signAsync({ sub: 'test-user', email: 'test@test.com', role: 'USER' });
    const res = await request(app.getHttpServer())
      .get('/api/users/me')
      .set('Authorization', `Bearer ${token}`);
    expect(res.body.success).toBe(true);
  });

  it('disabled user → 401', async () => {
    const token = await jwtService.signAsync({ sub: 'disabled-user', email: 'disabled@test.com', role: 'USER' });
    const res = await request(app.getHttpServer())
      .get('/api/users/me')
      .set('Authorization', `Bearer ${token}`);
    expect(res.body.success).toBe(false);
    expect(res.body.statusCode).toBe(401);
    expect(res.body.message).toContain('禁用');
  });

  it('deleted user → 401', async () => {
    const token = await jwtService.signAsync({ sub: 'deleted-user', email: 'deleted@test.com', role: 'USER' });
    const res = await request(app.getHttpServer())
      .get('/api/users/me')
      .set('Authorization', `Bearer ${token}`);
    expect(res.body.success).toBe(false);
    expect(res.body.statusCode).toBe(401);
    expect(res.body.message).toContain('注销');
  });

  it('non-existent user → 401', async () => {
    const token = await jwtService.signAsync({ sub: 'ghost-user', email: 'ghost@test.com', role: 'USER' });
    const res = await request(app.getHttpServer())
      .get('/api/users/me')
      .set('Authorization', `Bearer ${token}`);
    expect(res.body.success).toBe(false);
    expect(res.body.statusCode).toBe(401);
  });

  it('admin token (type=admin) rejected on /api/*', async () => {
    const token = await jwtService.signAsync({ sub: 'test-user', type: 'admin', role: 'SUPER_ADMIN' });
    const res = await request(app.getHttpServer())
      .get('/api/users/me')
      .set('Authorization', `Bearer ${token}`);
    expect(res.body.success).toBe(false);
    expect(res.body.statusCode).toBe(401);
    expect(res.body.message).toContain('无效');
  });
});

describe('H0-04 Refresh token user status check', () => {
  const OLD_ENV = { ...process.env };

  afterAll(() => {
    process.env = OLD_ENV;
  });

  let app: INestApplication;

  beforeAll(async () => {
    process.env.NODE_ENV = 'development';
    process.env.JWT_SECRET = 'test-jwt-h0-04';
    delete process.env.APPLE_BUNDLE_ID;
    const m: TestingModule = await Test.createTestingModule({ imports: [AppModule] }).compile();
    app = m.createNestApplication();
    app.setGlobalPrefix('api', { exclude: ['admin-api/(.*)', 'internal/(.*)'] });
    await app.init();
  });

  afterAll(async () => { await app.close(); });

  it('Apple login → get refreshToken → refresh succeeds (active user)', async () => {
    // Login to get a refresh token
    const loginRes = await request(app.getHttpServer())
      .post('/api/auth/apple')
      .send({ identityToken: 'test-token-for-refresh' });
    expect(loginRes.body.success).toBe(true);

    const { refreshToken } = loginRes.body.data;
    expect(refreshToken).toBeTruthy();

    // Refresh with the token
    const refreshRes = await request(app.getHttpServer())
      .post('/api/auth/refresh')
      .send({ refreshToken });
    expect(refreshRes.body.success).toBe(true);
    expect(refreshRes.body.data).toHaveProperty('accessToken');
    expect(refreshRes.body.data).toHaveProperty('refreshToken');
  });

  it('POST /api/auth/refresh with invalid token → 401', async () => {
    const res = await request(app.getHttpServer())
      .post('/api/auth/refresh')
      .send({ refreshToken: 'invalid-or-expired-token' });
    expect(res.body.success).toBe(false);
    expect(res.body.statusCode).toBe(401);
  });
});

describe('H0-06 CAPI DTO validation', () => {
  const OLD_ENV = { ...process.env };

  afterAll(() => {
    process.env = OLD_ENV;
  });

  const INTERNAL_KEY = 'test-internal-key-dto';

  let app: INestApplication;
  let jwtService: JwtService;

  beforeAll(async () => {
    process.env.NODE_ENV = 'development';
    process.env.JWT_SECRET = 'test-jwt-h0-06';
    process.env.INTERNAL_API_KEY = INTERNAL_KEY;
    delete process.env.APPLE_BUNDLE_ID;
    const m: TestingModule = await Test.createTestingModule({ imports: [AppModule] }).compile();
    app = m.createNestApplication();
    app.setGlobalPrefix('api', { exclude: ['admin-api/(.*)', 'internal/(.*)'] });
    await app.init();
    jwtService = app.get(JwtService);
  });

  afterAll(async () => { await app.close(); });

  async function getUserToken(): Promise<string> {
    return jwtService.signAsync({ sub: 'test-user', email: 'test@test.com', role: 'USER', type: 'user' });
  }

  describe('POST /api/imports', () => {
    it('valid DTO → 201', async () => {
      const token = await getUserToken();
      const res = await request(app.getHttpServer())
        .post('/api/imports')
        .set('Authorization', `Bearer ${token}`)
        .send({ fileName: 'test.pdf', sourceType: 'file' });
      expect(res.body.success).toBe(true);
      expect(res.body.data).toHaveProperty('jobId');
      expect(res.body.data).toHaveProperty('status', 'queued');
    });
  });

  describe('POST /api/knowledge-bases/:kbId/sources', () => {
    it('valid DTO → 201', async () => {
      const token = await getUserToken();
      const res = await request(app.getHttpServer())
        .post('/api/knowledge-bases/test-kb/sources')
        .set('Authorization', `Bearer ${token}`)
        .send({ title: '我的笔记', type: 'file', originalFilename: 'notes.pdf' });
      expect(res.body.success).toBe(true);
      expect(res.body.data).toHaveProperty('id');
    });

    it('invalid type value → 400', async () => {
      const token = await getUserToken();
      const res = await request(app.getHttpServer())
        .post('/api/knowledge-bases/test-kb/sources')
        .set('Authorization', `Bearer ${token}`)
        .send({ type: 'invalid_type_value' });
      expect(res.body.success).toBe(false);
      expect(res.body.statusCode).toBe(400);
    });
  });

  describe('POST /api/learning-sessions', () => {
    it('valid DTO → 201', async () => {
      const token = await getUserToken();
      const res = await request(app.getHttpServer())
        .post('/api/learning-sessions')
        .set('Authorization', `Bearer ${token}`)
        .send({ mode: 'active_recall', knowledgeBaseId: 'kb-1' });
      expect(res.body.success).toBe(true);
    });

    it('invalid mode → 400', async () => {
      const token = await getUserToken();
      const res = await request(app.getHttpServer())
        .post('/api/learning-sessions')
        .set('Authorization', `Bearer ${token}`)
        .send({ mode: 'invalid_mode' });
      expect(res.body.success).toBe(false);
      expect(res.body.statusCode).toBe(400);
    });
  });
});
