import { AppErrorAction } from "./app-error-action.js"
import { AppError, AppErrorDefinition, AppErrorKey, getErrorMessage } from "./app-error.js"
import { BaseException, ExceptionContext } from "./base-exception.js"

/**
 * Application exception - wraps an AppError definition
 * Similar to Java AppException
 */
export class AppException extends BaseException {
  private readonly error: AppErrorDefinition
  private readonly args: unknown[]

  constructor(error: AppErrorDefinition, ...args: unknown[])
  constructor(error: AppErrorDefinition, context: ExceptionContext, ...args: unknown[])
  constructor(error: AppErrorDefinition, contextOrArg?: ExceptionContext | unknown, ...args: unknown[]) {
    // Determine if second parameter is context or an argument
    let context: ExceptionContext | undefined
    let allArgs: unknown[]

    if (
      contextOrArg !== undefined &&
      typeof contextOrArg === "object" &&
      contextOrArg !== null &&
      ("requestId" in contextOrArg ||
        "traceId" in contextOrArg ||
        "correlationId" in contextOrArg ||
        "userId" in contextOrArg ||
        "endpoint" in contextOrArg)
    ) {
      context = contextOrArg as ExceptionContext
      allArgs = args
    } else if (contextOrArg !== undefined) {
      context = undefined
      allArgs = [contextOrArg, ...args]
    } else {
      context = undefined
      allArgs = args
    }

    super(getErrorMessage(error, ...allArgs), context)
    this.error = error
    this.args = allArgs
  }

  getHttpStatus(): number {
    return this.error.httpErrorCode
  }

  getMessage(): string {
    return getErrorMessage(this.error, ...this.args)
  }

  getAppErrorCode(): string {
    return this.error.appErrorCode
  }

  getErrorAction(): AppErrorAction {
    return this.error.errorAction
  }

  getTitle(): string {
    return this.error.title
  }

  getErrorType(): string {
    return this.error.errorType
  }

  getReferenceDoc(): string | null {
    return this.error.referenceDoc
  }

  getDownstreamErrorMessage(): string | null {
    // Downstream error message is not available for app errors
    return null
  }

  getDownstreamErrorCode(): string | null {
    // Downstream error code is not available for app errors
    return null
  }

  /** Get the underlying error definition */
  getErrorDefinition(): AppErrorDefinition {
    return this.error
  }

  /** Get the arguments used to format the message */
  getArgs(): unknown[] {
    return this.args
  }
}

/**
 * Downstream exception - wraps errors from external services
 */
export class DownstreamException extends BaseException {
  private readonly error: AppErrorDefinition
  private readonly args: unknown[]
  private readonly downstreamMessage: string | null
  private readonly downstreamCode: string | null
  private readonly serviceName: string

  constructor(
    error: AppErrorDefinition,
    serviceName: string,
    downstreamMessage?: string,
    downstreamCode?: string,
    context?: ExceptionContext,
    ...args: unknown[]
  ) {
    super(getErrorMessage(error, ...args), context)
    this.error = error
    this.serviceName = serviceName
    this.downstreamMessage = downstreamMessage ?? null
    this.downstreamCode = downstreamCode ?? null
    this.args = args
  }

  getHttpStatus(): number {
    return this.error.httpErrorCode
  }

  getAppErrorCode(): string {
    return this.error.appErrorCode
  }

  getErrorAction(): AppErrorAction {
    return this.error.errorAction
  }

  getTitle(): string {
    return this.error.title
  }

  getErrorType(): string {
    return this.error.errorType
  }

  getDownstreamErrorMessage(): string | null {
    return this.downstreamMessage
  }

  getDownstreamErrorCode(): string | null {
    return this.downstreamCode
  }

  getServiceName(): string {
    return this.serviceName
  }
}

// Convenience factory functions for common errors

export function invalidParameter(param: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.INVALID_PARAMETER, context, param)
  }
  return new AppException(AppError.INVALID_PARAMETER, param)
}

export function unauthorizedAccess(context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.UNAUTHORIZED_ACCESS, context)
  }
  return new AppException(AppError.UNAUTHORIZED_ACCESS)
}

export function notFound(resource: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.GENERIC_NOT_FOUND, context, resource)
  }
  return new AppException(AppError.GENERIC_NOT_FOUND, resource)
}

export function badRequest(reason: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.GENERIC_BAD_REQUEST, context, reason)
  }
  return new AppException(AppError.GENERIC_BAD_REQUEST, reason)
}

export function internalServerError(details: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.GENERIC_INTERNAL_SERVER_ERROR, context, details)
  }
  return new AppException(AppError.GENERIC_INTERNAL_SERVER_ERROR, details)
}

export function validationFailure(errors: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.VALIDATION_FAILURE, context, errors)
  }
  return new AppException(AppError.VALIDATION_FAILURE, errors)
}

export function tooManyRequests(context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.TOO_MANY_REQUESTS, context)
  }
  return new AppException(AppError.TOO_MANY_REQUESTS)
}

export function jwtAuthenticationFailure(context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.JWT_AUTHENTICATION_FAILURE, context)
  }
  return new AppException(AppError.JWT_AUTHENTICATION_FAILURE)
}

export function invalidApiKey(context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.INVALID_API_KEY, context)
  }
  return new AppException(AppError.INVALID_API_KEY)
}

export function subscriptionNotFound(userId: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.SUBSCRIPTION_NOT_FOUND, context, userId)
  }
  return new AppException(AppError.SUBSCRIPTION_NOT_FOUND, userId)
}

export function subscriptionLimitReached(limit: number, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.SUBSCRIPTION_LIMIT_REACHED, context, limit)
  }
  return new AppException(AppError.SUBSCRIPTION_LIMIT_REACHED, limit)
}

export function optimizationNotFound(id: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.OPTIMIZATION_NOT_FOUND, context, id)
  }
  return new AppException(AppError.OPTIMIZATION_NOT_FOUND, id)
}

export function optimizationLimitExceeded(
  used: number,
  limit: number,
  context?: ExceptionContext,
): AppException {
  if (context) {
    return new AppException(AppError.OPTIMIZATION_LIMIT_EXCEEDED, context, used, limit)
  }
  return new AppException(AppError.OPTIMIZATION_LIMIT_EXCEEDED, used, limit)
}

export function githubApiError(message: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.GITHUB_API_ERROR, context, message)
  }
  return new AppException(AppError.GITHUB_API_ERROR, message)
}

export function githubInstallationNotFound(owner: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.GITHUB_INSTALLATION_NOT_FOUND, context, owner)
  }
  return new AppException(AppError.GITHUB_INSTALLATION_NOT_FOUND, owner)
}

export function databaseError(message: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.DATABASE_CONNECTION_ERROR, context, message)
  }
  return new AppException(AppError.DATABASE_CONNECTION_ERROR, message)
}

export function externalServiceError(
  service: string,
  message: string,
  context?: ExceptionContext,
): AppException {
  if (context) {
    return new AppException(AppError.EXTERNAL_SERVICE_ERROR, context, service, message)
  }
  return new AppException(AppError.EXTERNAL_SERVICE_ERROR, service, message)
}

// New factory functions for additional error types

export function missingRequiredFields(fields: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.MISSING_REQUIRED_FIELDS, context, fields)
  }
  return new AppException(AppError.MISSING_REQUIRED_FIELDS, fields)
}

export function conflict(resource: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.CONFLICT, context, resource)
  }
  return new AppException(AppError.CONFLICT, resource)
}

export function unprocessableEntity(reason: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.UNPROCESSABLE_ENTITY, context, reason)
  }
  return new AppException(AppError.UNPROCESSABLE_ENTITY, reason)
}

export function missingAuthorizationHeader(context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.MISSING_AUTHORIZATION_HEADER, context)
  }
  return new AppException(AppError.MISSING_AUTHORIZATION_HEADER)
}

export function missingUserId(context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.MISSING_USER_ID, context)
  }
  return new AppException(AppError.MISSING_USER_ID)
}

export function unauthorized(reason?: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.UNAUTHORIZED, context, reason ?? "")
  }
  return new AppException(AppError.UNAUTHORIZED, reason ?? "")
}

export function githubNotCollaborator(repo: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.GITHUB_NOT_COLLABORATOR, context, repo)
  }
  return new AppException(AppError.GITHUB_NOT_COLLABORATOR, repo)
}

export function githubBranchNotFound(branch: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.GITHUB_BRANCH_NOT_FOUND, context, branch)
  }
  return new AppException(AppError.GITHUB_BRANCH_NOT_FOUND, branch)
}

export function githubPrNotFound(prInfo: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.GITHUB_PR_NOT_FOUND, context, prInfo)
  }
  return new AppException(AppError.GITHUB_PR_NOT_FOUND, prInfo)
}

export function githubInstallationError(message: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.GITHUB_INSTALLATION_ERROR, context, message)
  }
  return new AppException(AppError.GITHUB_INSTALLATION_ERROR, message)
}

export function optimizationRejected(reason?: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.OPTIMIZATION_REJECTED, context, reason ?? "")
  }
  return new AppException(AppError.OPTIMIZATION_REJECTED, reason ?? "")
}

export function subscriptionInactive(context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.SUBSCRIPTION_INACTIVE, context)
  }
  return new AppException(AppError.SUBSCRIPTION_INACTIVE)
}

export function forbidden(reason?: string, context?: ExceptionContext): AppException {
  if (context) {
    return new AppException(AppError.FORBIDDEN, context, reason ?? "")
  }
  return new AppException(AppError.FORBIDDEN, reason ?? "")
}
