import { getDb } from '../db'
import { Context } from '../types/context'
import { AppErrors } from '../lib/errors'
import crypto from 'crypto'

export interface ApiKey {
  id: string
  name: string
  keyPrefix: string
  keyHash: string
  userId: string
  tenantId?: string
  permissions: string[]
  lastUsedAt?: Date
  expiresAt?: Date
  revoked: boolean
  revokedAt?: Date
  createdAt: Date
  updatedAt: Date
}

export interface CreateApiKeyInput {
  name: string
  permissions?: string[]
  expiresAt?: Date
}

export interface UpdateApiKeyInput {
  name?: string
  permissions?: string[]
  expiresAt?: Date
}

/**
 * Generate a new API key
 */
function generateApiKey(): { key: string; hash: string } {
  const key = `sk_live_${crypto.randomBytes(32).toString('hex')}`
  const hash = crypto.createHash('sha256').update(key).digest('hex')
  return { key, hash }
}

/**
 * Create a new API key for the current user
 */
export async function createApiKey(
  context: Context,
  input: CreateApiKeyInput
): Promise<{ id: string; name: string; key: string; createdAt: Date }> {
  if (!context.user) {
    throw AppErrors.unauthenticated('Authentication required')
  }

  const db = getDb()
  const { key, hash } = generateApiKey()
  const keyPrefix = key.substring(0, 12) // First 12 chars for display

  // Get tenant_id from context if available
  const tenantId = context.tenantContext?.tenantId || null

  const result = await db.query(
    `INSERT INTO api_keys (name, key_prefix, key_hash, user_id, tenant_id, permissions, expires_at, created_at, updated_at)
     VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())
     RETURNING id, name, key_prefix, created_at`,
    [
      input.name,
      keyPrefix,
      hash,
      context.user.id,
      tenantId,
      JSON.stringify(input.permissions || ['read', 'write']),
      input.expiresAt || null,
    ]
  )

  return {
    id: result.rows[0].id,
    name: result.rows[0].name,
    key, // Return full key only once
    createdAt: result.rows[0].created_at,
  }
}

/**
 * Get all API keys for the current user
 */
export async function getApiKeys(context: Context): Promise<ApiKey[]> {
  if (!context.user) {
    throw AppErrors.unauthenticated('Authentication required')
  }

  const db = getDb()
  const result = await db.query(
    `SELECT id, name, key_prefix, user_id, tenant_id, permissions, last_used_at, expires_at, revoked, revoked_at, created_at, updated_at
     FROM api_keys
     WHERE user_id = $1 AND revoked = false
     ORDER BY created_at DESC`,
    [context.user.id]
  )

  return result.rows.map((row) => ({
    id: row.id,
    name: row.name,
    keyPrefix: row.key_prefix,
    keyHash: '', // Don't return hash
    userId: row.user_id,
    tenantId: row.tenant_id,
    permissions: JSON.parse(row.permissions || '[]'),
    lastUsedAt: row.last_used_at,
    expiresAt: row.expires_at,
    revoked: row.revoked,
    revokedAt: row.revoked_at,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  }))
}

/**
 * Get a single API key by ID
 */
export async function getApiKey(context: Context, id: string): Promise<ApiKey> {
  if (!context.user) {
    throw AppErrors.unauthenticated('Authentication required')
  }

  const db = getDb()
  const result = await db.query(
    `SELECT id, name, key_prefix, user_id, tenant_id, permissions, last_used_at, expires_at, revoked, revoked_at, created_at, updated_at
     FROM api_keys
     WHERE id = $1 AND user_id = $2`,
    [id, context.user.id]
  )

  if (result.rows.length === 0) {
    throw AppErrors.notFound('API key not found')
  }

  const row = result.rows[0]
  return {
    id: row.id,
    name: row.name,
    keyPrefix: row.key_prefix,
    keyHash: '',
    userId: row.user_id,
    tenantId: row.tenant_id,
    permissions: JSON.parse(row.permissions || '[]'),
    lastUsedAt: row.last_used_at,
    expiresAt: row.expires_at,
    revoked: row.revoked,
    revokedAt: row.revoked_at,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  }
}

/**
 * Update an API key
 */
export async function updateApiKey(
  context: Context,
  id: string,
  input: UpdateApiKeyInput
): Promise<ApiKey> {
  if (!context.user) {
    throw AppErrors.unauthenticated('Authentication required')
  }

  const db = getDb()
  const updates: string[] = []
  const values: unknown[] = []
  let paramCount = 1

  if (input.name !== undefined) {
    updates.push(`name = $${paramCount++}`)
    values.push(input.name)
  }
  if (input.permissions !== undefined) {
    updates.push(`permissions = $${paramCount++}`)
    values.push(JSON.stringify(input.permissions))
  }
  if (input.expiresAt !== undefined) {
    updates.push(`expires_at = $${paramCount++}`)
    values.push(input.expiresAt)
  }

  if (updates.length === 0) {
    return getApiKey(context, id)
  }

  updates.push(`updated_at = NOW()`)
  values.push(id, context.user.id)

  const result = await db.query(
    `UPDATE api_keys
     SET ${updates.join(', ')}
     WHERE id = $${paramCount++} AND user_id = $${paramCount++} AND revoked = false
     RETURNING id, name, key_prefix, user_id, tenant_id, permissions, last_used_at, expires_at, revoked, revoked_at, created_at, updated_at`,
    values
  )

  if (result.rows.length === 0) {
    throw AppErrors.notFound('API key not found')
  }

  const row = result.rows[0]
  return {
    id: row.id,
    name: row.name,
    keyPrefix: row.key_prefix,
    keyHash: '',
    userId: row.user_id,
    tenantId: row.tenant_id,
    permissions: JSON.parse(row.permissions || '[]'),
    lastUsedAt: row.last_used_at,
    expiresAt: row.expires_at,
    revoked: row.revoked,
    revokedAt: row.revoked_at,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  }
}

/**
 * Revoke (delete) an API key
 */
export async function revokeApiKey(context: Context, id: string): Promise<boolean> {
  if (!context.user) {
    throw AppErrors.unauthenticated('Authentication required')
  }

  const db = getDb()
  const result = await db.query(
    `UPDATE api_keys
     SET revoked = true, revoked_at = NOW(), updated_at = NOW()
     WHERE id = $1 AND user_id = $2 AND revoked = false
     RETURNING id`,
    [id, context.user.id]
  )

  if (result.rows.length === 0) {
    throw AppErrors.notFound('API key not found')
  }

  return true
}

/**
 * Verify an API key and return user context
 */
export async function verifyApiKey(key: string): Promise<{ userId: string; tenantId?: string; permissions: string[] } | null> {
  const db = getDb()
  const hash = crypto.createHash('sha256').update(key).digest('hex')

  const result = await db.query(
    `SELECT user_id, tenant_id, permissions, expires_at, revoked
     FROM api_keys
     WHERE key_hash = $1`,
    [hash]
  )

  if (result.rows.length === 0) {
    return null
  }

  const row = result.rows[0]

  if (row.revoked) {
    return null
  }

  if (row.expires_at && new Date(row.expires_at) < new Date()) {
    return null
  }

  // Update last used timestamp
  await db.query(
    `UPDATE api_keys SET last_used_at = NOW() WHERE key_hash = $1`,
    [hash]
  )

  return {
    userId: row.user_id,
    tenantId: row.tenant_id,
    permissions: JSON.parse(row.permissions || '[]'),
  }
}

