import { NextResponse } from 'next/server'
import { recordSecurityEvent } from '../security/security-observability'

export interface ApiValidationIssue {
  field: string
  code: 'required' | 'invalid_type' | 'invalid_value' | 'invalid_json'
  message: string
}

export type ApiValidationResult<T> =
  | { valid: true; data: T }
  | { valid: false; issues: ApiValidationIssue[] }

export type ApiValidator<T> = (input: unknown) => ApiValidationResult<T>

function success<T>(data: T): ApiValidationResult<T> {
  return { valid: true, data }
}

function failure<T>(issues: ApiValidationIssue[]): ApiValidationResult<T> {
  return { valid: false, issues }
}

export function validationIssue(
  field: string,
  code: ApiValidationIssue['code'],
  message: string,
): ApiValidationIssue {
  return { field, code, message }
}

export function asObject(input: unknown, field = 'body'): ApiValidationResult<Record<string, unknown>> {
  if (!input || typeof input !== 'object' || Array.isArray(input)) {
    return failure([
      validationIssue(field, 'invalid_type', 'Expected a JSON object'),
    ])
  }

  return success(input as Record<string, unknown>)
}

export function requiredString(
  input: Record<string, unknown>,
  key: string,
  field = key,
): ApiValidationResult<string> {
  const value = input[key]
  if (value === undefined || value === null || value === '') {
    return failure([validationIssue(field, 'required', `${field} is required`)])
  }

  if (typeof value !== 'string') {
    return failure([validationIssue(field, 'invalid_type', `${field} must be a string`)])
  }

  return success(value)
}

export function optionalString(
  input: Record<string, unknown>,
  key: string,
  field = key,
): ApiValidationResult<string | undefined> {
  const value = input[key]
  if (value === undefined || value === null || value === '') {
    return success(undefined)
  }
  if (typeof value !== 'string') {
    return failure([validationIssue(field, 'invalid_type', `${field} must be a string`)])
  }
  return success(value)
}

export function optionalBoolean(
  input: Record<string, unknown>,
  key: string,
  field = key,
): ApiValidationResult<boolean | undefined> {
  const value = input[key]
  if (value === undefined || value === null) {
    return success(undefined)
  }
  if (typeof value !== 'boolean') {
    return failure([validationIssue(field, 'invalid_type', `${field} must be a boolean`)])
  }
  return success(value)
}

export function optionalNumber(
  input: Record<string, unknown>,
  key: string,
  field = key,
): ApiValidationResult<number | undefined> {
  const value = input[key]
  if (value === undefined || value === null || value === '') {
    return success(undefined)
  }

  const numericValue = typeof value === 'number' ? value : Number(value)
  if (Number.isNaN(numericValue)) {
    return failure([validationIssue(field, 'invalid_type', `${field} must be a number`)])
  }

  return success(numericValue)
}

export function optionalArray<T>(
  input: Record<string, unknown>,
  key: string,
  mapper: (value: unknown, index: number) => T | null,
  field = key,
): ApiValidationResult<T[] | undefined> {
  const value = input[key]
  if (value === undefined || value === null) {
    return success(undefined)
  }
  if (!Array.isArray(value)) {
    return failure([validationIssue(field, 'invalid_type', `${field} must be an array`)])
  }

  const mapped = value
    .map((entry, index) => mapper(entry, index))
    .filter((entry): entry is T => entry !== null)

  return success(mapped)
}

export async function validateJsonBody<T>(
  request: Request,
  validator: ApiValidator<T>,
): Promise<ApiValidationResult<T>> {
  let body: unknown
  try {
    body = await request.json()
  } catch {
    return failure([
      validationIssue('body', 'invalid_json', 'Request body must contain valid JSON'),
    ])
  }
  return validator(body)
}

export function validationErrorResponse(
  issues: ApiValidationIssue[],
  status = 400,
  context: { endpoint?: string } = {},
): NextResponse {
  recordSecurityEvent('request_validation_failed', {
    endpoint: context.endpoint,
    issues: issues.map((issue) => ({
      field: issue.field,
      code: issue.code,
    })),
  }, 'info')

  return NextResponse.json(
    {
      error: {
        code: 'VALIDATION_FAILED',
        message: 'Request validation failed',
        issues,
      },
    },
    { status },
  )
}
