import request from 'supertest'
import { Test, TestingModule } from '@nestjs/testing'
import { INestApplication, ValidationPipe } from '@nestjs/common'
import { getModelToken } from '@nestjs/mongoose'
import { JwtService } from '@nestjs/jwt'
import { WechatPayService } from '../src/modules/payment/wechat-pay.service'
import { AppModule } from '../src/app.module'

function makeChain(execValue: any = null) {
  const chain: Record<string, jest.Mock> = {}
  const methods = ['find', 'findOne', 'findById', 'findByIdAndUpdate', 'sort', 'skip', 'limit', 'select', 'lean', 'countDocuments']
  for (const m of methods) {
    chain[m] = jest.fn(() => chain)
  }
  chain.exec = jest.fn().mockResolvedValue(execValue)
  chain.save = jest.fn().mockResolvedValue(true)
  return chain
}

function mockModel(extra: Record<string, any> = {}, defaultExecValue: any = null) {
  const chain = makeChain(defaultExecValue)
  return {
    find: jest.fn(() => chain),
    findOne: jest.fn(() => chain),
    findById: jest.fn(() => chain),
    findByIdAndUpdate: jest.fn(() => chain),
    create: jest.fn(),
    countDocuments: jest.fn(() => chain),
    ...extra,
  }
}

describe('API Integration (e2e)', () => {
  let app: INestApplication
  let authToken: string

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    })
      .overrideProvider(WechatPayService).useValue({
        nativePay: jest.fn().mockResolvedValue({ codeUrl: 'weixin://pay/test' }),
        jsapiPay: jest.fn().mockResolvedValue({ paySign: 'test', nonceStr: 'test', package: 'test', timeStamp: '0', signType: 'RSA' }),
        queryOrder: jest.fn().mockResolvedValue({ trade_state: 'SUCCESS' }),
        verifyAndDecrypt: jest.fn().mockReturnValue({ out_trade_no: 'TEST', transaction_id: 'wx_test' }),
      })
      .overrideProvider(getModelToken('User')).useValue(mockModel({ countDocuments: jest.fn().mockResolvedValue(0) }))
      .overrideProvider(getModelToken('Interview')).useValue(mockModel({}, []))
      .overrideProvider(getModelToken('PaymentOrder')).useValue(mockModel({}, null))
      .overrideProvider(getModelToken('Progress')).useValue(mockModel({}, null))
      .overrideProvider(getModelToken('CompanyBank')).useValue(mockModel())
      .overrideProvider(getModelToken('Contribution')).useValue(mockModel())
      .overrideProvider(getModelToken('SiteConfig')).useValue(mockModel())
      .overrideProvider(getModelToken('Resume')).useValue(mockModel())
      .compile()

    app = moduleFixture.createNestApplication()
    app.setGlobalPrefix('api')
    app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }))
    await app.init()

    const jwtService = app.get(JwtService)
    authToken = jwtService.sign({ userId: '507f1f77bcf86cd799439011', phone: '13800138000' })
  })

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

  afterEach(() => {
    jest.clearAllMocks()
  })

  describe('Unauthenticated access', () => {
    it('GET /api/user/info should return 401', async () => {
      const res = await request(app.getHttpServer()).get('/api/user/info')
      expect(res.status).toBe(401)
    })

    it('POST /api/user/send-code should be public', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/user/send-code')
        .send({ phone: '13800138000' })
      expect(res.status).toBe(201)
    })

    it('POST /api/payment/create should return 401', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/payment/create')
        .send({ plan: 'growth' })
      expect(res.status).toBe(401)
    })

    it('GET /api/progress should return 401', async () => {
      const res = await request(app.getHttpServer()).get('/api/progress')
      expect(res.status).toBe(401)
    })

    it('POST /api/progress/checkin should return 401', async () => {
      const res = await request(app.getHttpServer()).post('/api/progress/checkin')
      expect(res.status).toBe(401)
    })
  })

  describe('Authenticated access', () => {
    const headers = () => ({ Authorization: `Bearer ${authToken}` })

    it('POST /api/payment/create should validate plan', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/payment/create')
        .set(headers())
        .send({ plan: 'invalid' })
      expect(res.status).toBe(400)
    })

    it('POST /api/payment/jsapi should handle missing wxOpenid', async () => {
      const userModel = app.get(getModelToken('User'))
      const chain = makeChain({ plan: 'free', wxOpenid: null })
      userModel.findById.mockReturnValueOnce(chain)

      const res = await request(app.getHttpServer())
        .post('/api/payment/jsapi')
        .set(headers())
        .send({ plan: 'growth' })
      expect(res.status).toBe(400)
    })

    it('GET /api/admin/check should check admin status', async () => {
      const userModel = app.get(getModelToken('User'))
      const chain = makeChain({ role: 'user' })
      userModel.findById.mockReturnValueOnce(chain)

      const res = await request(app.getHttpServer())
        .get('/api/admin/check')
        .set(headers())
      expect(res.status).toBe(200)
      expect(res.body.isAdmin).toBe(false)
    })

    it('POST /api/progress/redeem should validate item', async () => {
      const res = await request(app.getHttpServer())
        .post('/api/progress/redeem')
        .set(headers())
        .send({ item: 'nonexistent' })
      expect(res.status).toBe(400)
    })

    it('GET /api/progress should return default progress for new user', async () => {
      const progressModel = app.get(getModelToken('Progress'))
      const nullChain = makeChain(null)
      progressModel.findOne.mockReturnValueOnce(nullChain)
      progressModel.create.mockResolvedValue({
        userId: '507f1f77bcf86cd799439011',
        totalInterviews: 0,
        completedInterviews: 0,
        streak: 0,
        points: 0,
        totalCheckins: 0,
        recentScores: [],
        streakHistory: [],
        lastCheckinDate: null,
      })

      const res = await request(app.getHttpServer())
        .get('/api/progress')
        .set(headers())
      expect(res.status).toBe(200)
      expect(res.body).toHaveProperty('totalInterviews', 0)
      expect(res.body).toHaveProperty('points')
    })
  })

  describe('Payment flow', () => {
    const headers = () => ({ Authorization: `Bearer ${authToken}` })

    it('POST /api/payment/check should verify order ownership', async () => {
      const orderModel = app.get(getModelToken('PaymentOrder'))
      const chain = makeChain(null)
      orderModel.findOne.mockReturnValueOnce(chain)

      const res = await request(app.getHttpServer())
        .get('/api/payment/check/ORD123')
        .set(headers())
      expect(res.status).toBe(404)
    })
  })
})
