import crypto from 'node:crypto'
import type { Request } from 'express'

export const TEST_SESSION_HEADER = 'x-hypertwist-test-session'

export interface TestSessionPayload {
  userId: string
  email: string
  plan?: string
  role?: string
  name?: string
  authMethod?: string
}

export interface SessionLike {
  getUserId(): string
  getAccessTokenPayload(): Record<string, unknown>
  mergeIntoAccessTokenPayload(nextPayload: Record<string, unknown>): Promise<void>
  revokeSession(): Promise<void>
}

interface VerifyTestSessionInput {
  headerValue: string | undefined
  secret: string
}

function normalizeTrimmed(value: string | undefined) {
  return String(value || '').trim()
}

function encodeBase64Url(value: string) {
  return Buffer.from(value, 'utf8').toString('base64url')
}

function decodeBase64Url(value: string) {
  return Buffer.from(value, 'base64url').toString('utf8')
}

function buildSignature(encodedPayload: string, secret: string) {
  return crypto
    .createHmac('sha256', secret)
    .update(encodedPayload)
    .digest('base64url')
}

function isValidPayload(input: unknown): input is TestSessionPayload {
  if (!input || typeof input !== 'object') {
    return false
  }

  const payload = input as Record<string, unknown>
  return normalizeTrimmed(String(payload.userId || '')) !== '' && normalizeTrimmed(String(payload.email || '')) !== ''
}

export function createSignedTestSessionValue(payload: TestSessionPayload, secret: string) {
  const encodedPayload = encodeBase64Url(JSON.stringify(payload))
  const signature = buildSignature(encodedPayload, secret)
  return `${encodedPayload}.${signature}`
}

export function verifySignedTestSessionValue({
  headerValue,
  secret,
}: VerifyTestSessionInput): TestSessionPayload | null {
  const normalizedHeader = normalizeTrimmed(headerValue)
  const normalizedSecret = normalizeTrimmed(secret)
  if (!normalizedHeader || !normalizedSecret) {
    return null
  }

  const separatorIndex = normalizedHeader.lastIndexOf('.')
  if (separatorIndex <= 0 || separatorIndex === normalizedHeader.length - 1) {
    return null
  }

  const encodedPayload = normalizedHeader.slice(0, separatorIndex)
  const providedSignature = normalizedHeader.slice(separatorIndex + 1)
  const expectedSignature = buildSignature(encodedPayload, normalizedSecret)

  const providedBuffer = Buffer.from(providedSignature, 'base64url')
  const expectedBuffer = Buffer.from(expectedSignature, 'base64url')
  if (providedBuffer.length !== expectedBuffer.length) {
    return null
  }
  if (!crypto.timingSafeEqual(providedBuffer, expectedBuffer)) {
    return null
  }

  try {
    const parsed = JSON.parse(decodeBase64Url(encodedPayload))
    return isValidPayload(parsed) ? parsed : null
  } catch {
    return null
  }
}

export function readSignedTestSessionFromRequest(req: Request, secret: string): TestSessionPayload | null {
  const headerValue = req.header(TEST_SESSION_HEADER) || undefined
  return verifySignedTestSessionValue({
    headerValue,
    secret,
  })
}

export function createSessionLikeFromTestPayload(payload: TestSessionPayload): SessionLike {
  const accessTokenPayload: Record<string, unknown> = {
    email: payload.email,
    name: payload.name || payload.email.split('@')[0] || 'operator',
    plan: payload.plan || 'free',
    role: payload.role || 'operator',
    auth_method: payload.authMethod || 'email',
  }

  return {
    getUserId() {
      return payload.userId
    },
    getAccessTokenPayload() {
      return accessTokenPayload
    },
    async mergeIntoAccessTokenPayload(nextPayload: Record<string, unknown>) {
      Object.assign(accessTokenPayload, nextPayload)
    },
    async revokeSession() {
      Object.keys(accessTokenPayload).forEach((key) => {
        delete accessTokenPayload[key]
      })
    },
  }
}
