const { describe, test, expect, beforeEach, afterEach } = require('bun:test')
const crypto = require('crypto')
const bcrypt = require('bcrypt')

const VALID_PLANS = ['hobby', 'starter', 'professional', 'enterprise']
const PAID_PLANS = new Set(['starter', 'professional', 'enterprise'])
const PASSWORD_SALT_ROUNDS = 12
const USER_SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000
const ACCESS_TOKEN_TTL = 15 * 60
const REFRESH_TOKEN_TTL = 7 * 24 * 60 * 60

function isValidEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  return emailRegex.test(email)
}

function isValidPassword(password) {
  return password && password.length >= 8
}

function normalizePlan(plan) {
  return VALID_PLANS.includes(plan) ? plan : 'hobby'
}

function isPaidPlan(plan) {
  return PAID_PLANS.has(plan)
}

function generateAccessToken(payload, secret) {
  const jwt = require('jsonwebtoken')
  return jwt.sign({
    ...payload,
    iat: Math.floor(Date.now() / 1000),
    exp: Math.floor(Date.now() / 1000) + ACCESS_TOKEN_TTL
  }, secret, { algorithm: 'HS256' })
}

function verifyAccessToken(token, secret) {
  const jwt = require('jsonwebtoken')
  try {
    return jwt.verify(token, secret, { algorithms: ['HS256'] })
  } catch (error) {
    return null
  }
}

function parseCookies(cookieHeader) {
  const cookies = {}
  if (!cookieHeader) return cookies
  
  cookieHeader.split(';').forEach(cookie => {
    const [name, ...rest] = cookie.split('=')
    if (name && rest.length > 0) {
      cookies[name.trim()] = rest.join('=').trim()
    }
  })
  
  return cookies
}

function createMockRequest(options = {}) {
  return {
    method: options.method || 'GET',
    url: options.url || '/',
    headers: options.headers || {},
    body: options.body || {},
    ip: options.ip || '127.0.0.1',
    connection: { remoteAddress: options.ip || '127.0.0.1' }
  }
}

function createMockResponse() {
  const res = {
    statusCode: 200,
    headers: {},
    body: null,
    status(code) {
      this.statusCode = code
      return this
    },
    json(data) {
      this.body = data
      return this
    },
    setHeader(name, value) {
      this.headers[name] = value
      return this
    },
    end(data) {
      this.body = data
      return this
    }
  }
  return res
}

describe('API Endpoints', () => {
  describe('Authentication Endpoints', () => {
    const testSecret = 'test-jwt-secret-key-for-testing'

    describe('POST /api/auth/register', () => {
      test('validates email format', () => {
        expect(isValidEmail('test@example.com')).toBe(true)
        expect(isValidEmail('user.name@domain.co.uk')).toBe(true)
        expect(isValidEmail('invalid')).toBe(false)
        expect(isValidEmail('no@domain')).toBe(false)
        expect(isValidEmail('@nodomain.com')).toBe(false)
        expect(isValidEmail('')).toBe(false)
      })

      test('validates password strength', () => {
        expect(isValidPassword('password123')).toBe(true)
        expect(isValidPassword('short')).toBe(false)
        expect(isValidPassword('')).toBe(false)
        expect(isValidPassword(null)).toBe(false)
      })

      test('hashes password with bcrypt', async () => {
        const password = 'testPassword123'
        const hash = await bcrypt.hash(password, PASSWORD_SALT_ROUNDS)
        
        expect(hash).toBeDefined()
        expect(hash).not.toBe(password)
        expect(await bcrypt.compare(password, hash)).toBe(true)
      })

      test('rejects duplicate email', async () => {
        const users = [{ email: 'existing@example.com' }]
        const newEmail = 'existing@example.com'
        
        const exists = users.some(u => u.email === newEmail)
        expect(exists).toBe(true)
      })

      test('creates user with default plan', () => {
        const userData = {
          id: 'user-1',
          email: 'new@example.com',
          plan: 'hobby'
        }
        
        expect(normalizePlan(userData.plan)).toBe('hobby')
      })
    })

    describe('POST /api/auth/login', () => {
      test('validates credentials', async () => {
        const password = 'correctPassword'
        const hash = await bcrypt.hash(password, PASSWORD_SALT_ROUNDS)
        
        expect(await bcrypt.compare('correctPassword', hash)).toBe(true)
        expect(await bcrypt.compare('wrongPassword', hash)).toBe(false)
      })

      test('generates access token', () => {
        const payload = { userId: 'user-1', email: 'test@example.com' }
        const token = generateAccessToken(payload, testSecret)
        
        expect(token).toBeDefined()
        expect(token.split('.').length).toBe(3)
      })

      test('token contains user data', () => {
        const payload = { userId: 'user-1', email: 'test@example.com', plan: 'professional' }
        const token = generateAccessToken(payload, testSecret)
        const decoded = verifyAccessToken(token, testSecret)
        
        expect(decoded.userId).toBe('user-1')
        expect(decoded.email).toBe('test@example.com')
        expect(decoded.plan).toBe('professional')
      })

      test('rejects invalid token', () => {
        const decoded = verifyAccessToken('invalid-token', testSecret)
        expect(decoded).toBeNull()
      })

      test('tracks failed login attempts', () => {
        const attempts = new Map()
        const ip = '192.168.1.1'
        
        for (let i = 0; i < 5; i++) {
          const current = attempts.get(ip) || 0
          attempts.set(ip, current + 1)
        }
        
        expect(attempts.get(ip)).toBe(5)
      })
    })

    describe('POST /api/auth/logout', () => {
      test('clears session cookie', () => {
        const res = createMockResponse()
        res.setHeader('Set-Cookie', 'user_session=; Path=/; HttpOnly; Max-Age=0')
        
        expect(res.headers['Set-Cookie']).toContain('Max-Age=0')
      })

      test('blacklists access token', () => {
        const blacklist = new Set()
        const jti = crypto.randomUUID()
        
        blacklist.add(jti)
        
        expect(blacklist.has(jti)).toBe(true)
      })
    })

    describe('POST /api/auth/refresh', () => {
      test('validates refresh token', () => {
        const storedHash = 'salt:hash'
        const token = 'test-refresh-token'
        
        expect(storedHash.split(':').length).toBe(2)
      })

      test('generates new access token', () => {
        const payload = { userId: 'user-1', email: 'test@example.com' }
        const token = generateAccessToken(payload, testSecret)
        const decoded = verifyAccessToken(token, testSecret)
        
        expect(decoded.userId).toBe('user-1')
      })

      test('rotates refresh token', () => {
        const oldToken = crypto.randomBytes(64).toString('hex')
        const newToken = crypto.randomBytes(64).toString('hex')
        
        expect(oldToken).not.toBe(newToken)
      })
    })

    describe('POST /api/auth/forgot-password', () => {
      test('generates reset token', () => {
        const resetToken = crypto.randomBytes(32).toString('hex')
        
        expect(resetToken.length).toBe(64)
      })

      test('sets reset expiry', () => {
        const now = Date.now()
        const expiryMs = 60 * 60 * 1000
        const expiresAt = now + expiryMs
        
        expect(expiresAt).toBeGreaterThan(now)
      })

      test('does not reveal if email exists', () => {
        const response = { message: 'If the email exists, a reset link has been sent' }
        
        expect(response.message).not.toContain('not found')
      })
    })

    describe('POST /api/auth/reset-password', () => {
      test('validates reset token', () => {
        const tokens = new Map()
        tokens.set('valid-token', { userId: 'user-1', expiresAt: Date.now() + 3600000 })
        
        const tokenData = tokens.get('valid-token')
        expect(tokenData).toBeDefined()
      })

      test('rejects expired token', () => {
        const tokens = new Map()
        tokens.set('expired-token', { userId: 'user-1', expiresAt: Date.now() - 1000 })
        
        const tokenData = tokens.get('expired-token')
        const isValid = tokenData && tokenData.expiresAt > Date.now()
        
        expect(isValid).toBe(false)
      })

      test('hashes new password', async () => {
        const newPassword = 'newPassword123'
        const hash = await bcrypt.hash(newPassword, PASSWORD_SALT_ROUNDS)
        
        expect(await bcrypt.compare(newPassword, hash)).toBe(true)
      })
    })
  })

  describe('User Endpoints', () => {
    describe('GET /api/user/profile', () => {
      test('returns user data without sensitive fields', () => {
        const user = {
          id: 'user-1',
          email: 'test@example.com',
          name: 'Test User',
          plan: 'professional',
          passwordHash: '$2b$12$hash',
          twoFactorSecret: 'secret'
        }
        
        const safeUser = {
          id: user.id,
          email: user.email,
          name: user.name,
          plan: user.plan
        }
        
        expect(safeUser.passwordHash).toBeUndefined()
        expect(safeUser.twoFactorSecret).toBeUndefined()
      })

      test('requires authentication', () => {
        const req = createMockRequest({ headers: {} })
        const token = req.headers.authorization
        
        expect(token).toBeUndefined()
      })
    })

    describe('PUT /api/user/profile', () => {
      test('updates allowed fields', () => {
        const updates = { name: 'New Name' }
        const allowedFields = ['name', 'email']
        
        const filteredUpdates = {}
        Object.keys(updates).forEach(key => {
          if (allowedFields.includes(key)) {
            filteredUpdates[key] = updates[key]
          }
        })
        
        expect(filteredUpdates.name).toBe('New Name')
      })

      test('ignores protected fields', () => {
        const updates = { name: 'New Name', plan: 'enterprise', passwordHash: 'hash' }
        const allowedFields = ['name', 'email']
        
        const filteredUpdates = {}
        Object.keys(updates).forEach(key => {
          if (allowedFields.includes(key)) {
            filteredUpdates[key] = updates[key]
          }
        })
        
        expect(filteredUpdates.plan).toBeUndefined()
        expect(filteredUpdates.passwordHash).toBeUndefined()
      })
    })

    describe('GET /api/user/usage', () => {
      test('returns token usage', () => {
        const usage = {
          used: 50000,
          limit: 100000,
          remaining: 50000
        }
        
        expect(usage.remaining).toBe(usage.limit - usage.used)
      })

      test('calculates percentage used', () => {
        const used = 50000
        const limit = 100000
        const percentage = (used / limit) * 100
        
        expect(percentage).toBe(50)
      })
    })

    describe('GET /api/user/sessions', () => {
      test('lists active sessions', () => {
        const sessions = [
          { id: 'session-1', device: 'Chrome on Windows', lastAccessed: Date.now() },
          { id: 'session-2', device: 'Safari on iPhone', lastAccessed: Date.now() - 3600000 }
        ]
        
        expect(sessions.length).toBe(2)
      })

      test('excludes session tokens', () => {
        const sessions = [
          { id: 'session-1', token: 'secret-token', device: 'Chrome' }
        ]
        
        const safeSessions = sessions.map(s => ({ id: s.id, device: s.device }))
        
        expect(safeSessions[0].token).toBeUndefined()
      })
    })

    describe('DELETE /api/user/sessions/:id', () => {
      test('revokes specific session', () => {
        const sessions = new Set(['session-1', 'session-2'])
        const toDelete = 'session-1'
        
        sessions.delete(toDelete)
        
        expect(sessions.has(toDelete)).toBe(false)
        expect(sessions.size).toBe(1)
      })
    })
  })

  describe('Plan Endpoints', () => {
    describe('GET /api/plans', () => {
      test('returns all plans', () => {
        const plans = VALID_PLANS.map(plan => ({
          name: plan,
          tokenLimit: { hobby: 50000, starter: 100000, professional: 5000000, enterprise: 20000000 }[plan],
          appLimit: { hobby: 3, starter: 10, professional: 20, enterprise: Infinity }[plan],
          price: { hobby: 0, starter: 750, professional: 2500, enterprise: 7500 }[plan]
        }))
        
        expect(plans.length).toBe(4)
      })
    })

    describe('POST /api/subscription', () => {
      test('validates plan selection', () => {
        expect(normalizePlan('professional')).toBe('professional')
        expect(normalizePlan('invalid')).toBe('hobby')
      })

      test('validates billing cycle', () => {
        const cycles = ['monthly', 'yearly']
        
        expect(cycles.includes('monthly')).toBe(true)
        expect(cycles.includes('yearly')).toBe(true)
        expect(cycles.includes('weekly')).toBe(false)
      })

      test('calculates subscription price', () => {
        const prices = {
          starter_monthly_usd: 750,
          starter_yearly_usd: 7500
        }
        
        expect(prices.starter_yearly_usd).toBeLessThan(prices.starter_monthly_usd * 12)
      })
    })
  })

  describe('Admin Endpoints', () => {
    const adminSession = new Map()

    describe('POST /api/admin/login', () => {
      test('validates admin credentials', () => {
        const adminUser = 'admin@example.com'
        const adminPassword = 'adminPassword123'
        
        const credentials = { email: adminUser, password: adminPassword }
        
        expect(credentials.email).toBe(adminUser)
      })

      test('creates admin session', () => {
        const sessionId = crypto.randomUUID()
        adminSession.set(sessionId, { expiresAt: Date.now() + 86400000 })
        
        expect(adminSession.has(sessionId)).toBe(true)
      })

      test('rate limits failed attempts', () => {
        const attempts = { '192.168.1.1': 5 }
        const limit = 5
        
        expect(attempts['192.168.1.1']).toBeGreaterThanOrEqual(limit)
      })
    })

    describe('GET /api/admin/users', () => {
      test('requires admin auth', () => {
        const req = createMockRequest({ headers: { cookie: '' } })
        const cookies = parseCookies(req.headers.cookie)
        
        expect(cookies.admin_session).toBeUndefined()
      })

      test('returns paginated users', () => {
        const users = Array.from({ length: 25 }, (_, i) => ({ id: `user-${i}` }))
        const page = 1
        const limit = 10
        const offset = (page - 1) * limit
        
        const paginatedUsers = users.slice(offset, offset + limit)
        
        expect(paginatedUsers.length).toBe(10)
      })
    })

    describe('PUT /api/admin/users/:id/plan', () => {
      test('updates user plan', () => {
        const user = { id: 'user-1', plan: 'hobby' }
        const newPlan = 'professional'
        
        user.plan = normalizePlan(newPlan)
        
        expect(user.plan).toBe('professional')
      })

      test('rejects invalid plan', () => {
        const user = { id: 'user-1', plan: 'professional' }
        const invalidPlan = 'ultra'
        
        user.plan = normalizePlan(invalidPlan)
        
        expect(user.plan).toBe('hobby')
      })
    })

    describe('GET /api/admin/analytics', () => {
      test('returns usage statistics', () => {
        const stats = {
          totalUsers: 1000,
          activeUsers: 750,
          totalTokensUsed: 50000000,
          revenue: 150000
        }
        
        expect(stats.activeUsers).toBeLessThanOrEqual(stats.totalUsers)
      })
    })
  })

  describe('Middleware', () => {
    describe('Authentication Middleware', () => {
      test('extracts token from Authorization header', () => {
        const req = createMockRequest({
          headers: { authorization: 'Bearer test-token-123' }
        })
        
        const authHeader = req.headers.authorization
        const token = authHeader && authHeader.startsWith('Bearer ') 
          ? authHeader.substring(7) 
          : null
        
        expect(token).toBe('test-token-123')
      })

      test('extracts token from cookie', () => {
        const req = createMockRequest({
          headers: { cookie: 'access_token=cookie-token-456' }
        })
        
        const cookies = parseCookies(req.headers.cookie)
        const token = cookies.access_token || null
        
        expect(token).toBe('cookie-token-456')
      })

      test('prefers Authorization header over cookie', () => {
        const req = createMockRequest({
          headers: {
            authorization: 'Bearer header-token',
            cookie: 'access_token=cookie-token'
          }
        })
        
        const authHeader = req.headers.authorization
        const headerToken = authHeader && authHeader.startsWith('Bearer ')
          ? authHeader.substring(7)
          : null
        
        expect(headerToken).toBe('header-token')
      })
    })

    describe('Rate Limiting Middleware', () => {
      const rateLimiter = {
        requests: new Map(),
        windowMs: 60000,
        maxRequests: 100,
        
        check(key) {
          const now = Date.now()
          let record = this.requests.get(key)
          
          if (!record || now - record.windowStart > this.windowMs) {
            record = { count: 0, windowStart: now }
            this.requests.set(key, record)
          }
          
          record.count++
          return record.count <= this.maxRequests
        }
      }

      test('allows requests within limit', () => {
        for (let i = 0; i < 50; i++) {
          expect(rateLimiter.check('user-1')).toBe(true)
        }
      })

      test('blocks requests over limit', () => {
        rateLimiter.requests.clear()
        
        for (let i = 0; i < 100; i++) {
          rateLimiter.check('user-2')
        }
        
        expect(rateLimiter.check('user-2')).toBe(false)
      })
    })

    describe('CORS Middleware', () => {
      test('sets CORS headers', () => {
        const res = createMockResponse()
        res.setHeader('Access-Control-Allow-Origin', '*')
        res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
        res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
        
        expect(res.headers['Access-Control-Allow-Origin']).toBe('*')
      })
    })

    describe('Error Handling Middleware', () => {
      test('handles validation errors', () => {
        const error = { name: 'ValidationError', message: 'Invalid input' }
        const res = createMockResponse()
        
        res.status(400).json({ error: error.message })
        
        expect(res.statusCode).toBe(400)
        expect(res.body.error).toBe('Invalid input')
      })

      test('handles authentication errors', () => {
        const error = { name: 'UnauthorizedError', message: 'Invalid token' }
        const res = createMockResponse()
        
        res.status(401).json({ error: error.message })
        
        expect(res.statusCode).toBe(401)
      })

      test('handles not found errors', () => {
        const res = createMockResponse()
        
        res.status(404).json({ error: 'Resource not found' })
        
        expect(res.statusCode).toBe(404)
      })

      test('handles server errors', () => {
        const error = new Error('Internal server error')
        const res = createMockResponse()
        
        res.status(500).json({ error: 'Internal server error' })
        
        expect(res.statusCode).toBe(500)
      })
    })
  })

  describe('Request Validation', () => {
    describe('Body Validation', () => {
      test('validates JSON body', () => {
        const body = { email: 'test@example.com', password: 'password123' }
        
        expect(typeof body).toBe('object')
        expect(body.email).toBeDefined()
        expect(body.password).toBeDefined()
      })

      test('rejects missing required fields', () => {
        const body = { email: 'test@example.com' }
        const required = ['email', 'password']
        
        const missing = required.filter(field => !body[field])
        
        expect(missing).toContain('password')
      })

      test('validates field types', () => {
        const body = { email: 123, password: 'password' }
        
        expect(typeof body.email).not.toBe('string')
        expect(typeof body.password).toBe('string')
      })
    })

    describe('Query Parameter Validation', () => {
      test('parses pagination parameters', () => {
        const query = { page: '2', limit: '20' }
        
        const page = parseInt(query.page) || 1
        const limit = parseInt(query.limit) || 10
        
        expect(page).toBe(2)
        expect(limit).toBe(20)
      })

      test('applies defaults for missing parameters', () => {
        const query = {}
        
        const page = parseInt(query.page) || 1
        const limit = parseInt(query.limit) || 10
        
        expect(page).toBe(1)
        expect(limit).toBe(10)
      })

      test('validates parameter ranges', () => {
        const limit = 1000
        const maxLimit = 100
        
        const validLimit = Math.min(limit, maxLimit)
        
        expect(validLimit).toBe(100)
      })
    })

    describe('Path Parameter Validation', () => {
      test('validates user ID format', () => {
        const validId = 'user-123-abc'
        const invalidId = ''
        
        expect(validId.length).toBeGreaterThan(0)
        expect(invalidId.length).toBe(0)
      })

      test('validates UUID format', () => {
        const uuid = crypto.randomUUID()
        const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
        
        expect(uuidRegex.test(uuid)).toBe(true)
      })
    })
  })
})

describe('OAuth Integration', () => {
  const GOOGLE_CLIENT_ID = 'test-google-client-id'
  const GITHUB_CLIENT_ID = 'test-github-client-id'

  describe('Google OAuth', () => {
    test('generates OAuth state', () => {
      const state = crypto.randomBytes(32).toString('hex')
      
      expect(state.length).toBe(64)
    })

    test('builds authorization URL', () => {
      const redirectUri = 'https://example.com/auth/google/callback'
      const state = 'test-state'
      
      const url = new URL('https://accounts.google.com/o/oauth2/v2/auth')
      url.searchParams.set('client_id', GOOGLE_CLIENT_ID)
      url.searchParams.set('redirect_uri', redirectUri)
      url.searchParams.set('response_type', 'code')
      url.searchParams.set('scope', 'email profile')
      url.searchParams.set('state', state)
      
      expect(url.searchParams.get('client_id')).toBe(GOOGLE_CLIENT_ID)
      expect(url.searchParams.get('state')).toBe(state)
    })
  })

  describe('GitHub OAuth', () => {
    test('builds authorization URL', () => {
      const redirectUri = 'https://example.com/auth/github/callback'
      const state = 'test-state'
      
      const url = new URL('https://github.com/login/oauth/authorize')
      url.searchParams.set('client_id', GITHUB_CLIENT_ID)
      url.searchParams.set('redirect_uri', redirectUri)
      url.searchParams.set('scope', 'user:email')
      url.searchParams.set('state', state)
      
      expect(url.searchParams.get('client_id')).toBe(GITHUB_CLIENT_ID)
    })
  })
})
