import { NextRequest, NextResponse } from 'next/server'
import { validateCsrf } from './csrf'
import { getClientIpFromRequest, isIpBlocked, validateIpAccess } from './ip-allowlist'
import { rateLimitHeaders, type RateLimitResult } from './rate-limiter'
import { recordSecurityEvent } from './security-observability'

type IpEndpoint = 'sendEmail' | 'admin' | 'generatePdf' | 'webhooks'

type AuthProvider = {
  auth: (args: { headers: Headers }) => Promise<{ user?: unknown | null }>
}

type RateLimiter = {
  check: (identifier: string) => Promise<RateLimitResult>
}

export type ApiErrorCode =
  | 'ACCESS_DENIED'
  | 'UNAUTHORIZED'
  | 'FORBIDDEN'
  | 'CSRF_INVALID'
  | 'RATE_LIMITED'
  | 'SECURITY_UNAVAILABLE'
  | 'VALIDATION_FAILED'
  | 'INVALID_REQUEST'

export interface ApiGuardOptions {
  endpoint: string
  ipEndpoint?: IpEndpoint
  blocklistOnly?: boolean
  csrf?: 'none' | 'browser' | 'required'
  authProvider?: AuthProvider
  requireUser?: boolean
  rateLimiter?: RateLimiter
  rateLimitMax?: number
  rateLimitIdentifier?: string | ((args: { ip: string; user: unknown | null }) => string)
  onRateLimit?: (args: {
    endpoint: string
    ip: string
    user: unknown | null
    result: RateLimitResult
  }) => Promise<void> | void
}

export interface ApiGuardSuccess {
  ok: true
  ip: string
  user: unknown | null
  rateLimit?: RateLimitResult
}

export interface ApiGuardFailure {
  ok: false
  response: NextResponse
}

export type ApiGuardResult = ApiGuardSuccess | ApiGuardFailure

export function createApiErrorResponse(
  status: number,
  code: ApiErrorCode,
  message: string,
  details?: unknown,
  headers?: HeadersInit,
): NextResponse {
  return NextResponse.json(
    {
      error: message,
      code,
      details,
    },
    {
      status,
      headers,
    },
  )
}

export function isServerToServerRequest(req: NextRequest): boolean {
  const origin = req.headers.get('origin')
  const referer = req.headers.get('referer')
  return !origin && !referer
}

export async function runApiGuards(
  req: NextRequest,
  options: ApiGuardOptions,
): Promise<ApiGuardResult> {
  const csrfMode = options.csrf || 'browser'
  let ip = 'unknown'
  let user: unknown | null = null

  try {
    if (options.blocklistOnly) {
      ip = getClientIpFromRequest(req)
      if (isIpBlocked(ip)) {
        recordSecurityEvent('ip_access_denied', {
          endpoint: options.endpoint,
          ip,
          reason: `IP ${ip} is blocked`,
        })
        return {
          ok: false,
          response: createApiErrorResponse(403, 'ACCESS_DENIED', 'Access denied'),
        }
      }
    }

    if (options.ipEndpoint) {
      const ipCheck = validateIpAccess(req, options.ipEndpoint)
      ip = ipCheck.ip

      if (!ipCheck.allowed) {
        recordSecurityEvent('ip_access_denied', {
          endpoint: options.endpoint,
          ip,
          reason: ipCheck.reason,
        })
        return {
          ok: false,
          response: createApiErrorResponse(403, 'ACCESS_DENIED', 'Access denied'),
        }
      }
    }

    if (csrfMode !== 'none') {
      const shouldValidateCsrf = csrfMode === 'required' || !isServerToServerRequest(req)
      if (shouldValidateCsrf) {
        const csrfResult = validateCsrf(req)
        if (!csrfResult.valid) {
          recordSecurityEvent('csrf_blocked', {
            endpoint: options.endpoint,
            ip,
            reason: csrfResult.reason,
          })
          return {
            ok: false,
            response: createApiErrorResponse(403, 'CSRF_INVALID', 'CSRF validation failed'),
          }
        }
      }
    }

    if (options.authProvider) {
      const authResult = await options.authProvider.auth({ headers: req.headers })
      user = authResult.user || null
    }

    if (options.requireUser && !user) {
      recordSecurityEvent('auth_blocked', {
        endpoint: options.endpoint,
        ip,
        reason: 'Missing authenticated user',
      })
      return {
        ok: false,
        response: createApiErrorResponse(401, 'UNAUTHORIZED', 'Unauthorized'),
      }
    }

    let rateLimit: RateLimitResult | undefined
    if (options.rateLimiter) {
      const rateLimitIdentifier =
        typeof options.rateLimitIdentifier === 'function'
          ? options.rateLimitIdentifier({ ip, user })
          : options.rateLimitIdentifier ||
            (typeof user === 'object' && user && 'id' in user ? String((user as { id: unknown }).id) : ip)

      rateLimit = await options.rateLimiter.check(rateLimitIdentifier)
      if (!rateLimit.allowed) {
        recordSecurityEvent('rate_limit_blocked', {
          endpoint: options.endpoint,
          ip,
          limiter: options.rateLimiter?.constructor?.name || 'custom',
        })
        if (options.onRateLimit) {
          try {
            await options.onRateLimit({
              endpoint: options.endpoint,
              ip,
              user,
              result: rateLimit,
            })
          } catch {
            // Guard responses must not fail because optional callbacks fail.
          }
        }
        const headers =
          options.rateLimitMax && options.rateLimitMax > 0
            ? rateLimitHeaders(rateLimit, options.rateLimitMax)
            : undefined

        return {
          ok: false,
          response: createApiErrorResponse(
            429,
            'RATE_LIMITED',
            'Rate limit exceeded',
            {
              remaining: rateLimit.remaining,
              retryAfter: rateLimit.retryAfter,
            },
            headers,
          ),
        }
      }
    }

    return {
      ok: true,
      ip,
      user,
      rateLimit,
    }
  } catch (error) {
    recordSecurityEvent('auth_blocked', {
      endpoint: options.endpoint,
      ip,
      reason: error instanceof Error ? error.message : String(error),
    }, 'error')

    return {
      ok: false,
      response: createApiErrorResponse(
        503,
        'SECURITY_UNAVAILABLE',
        'Security checks unavailable',
      ),
    }
  }
}
