import type { ModuleDefinition } from "../shared/define-module"
import { RouteResolver } from "../shared/route-resolver"
import type {
  HTTPMethod,
  RequestContentType,
  RequestOptions,
} from "../types"
import type { HttpClient } from "./base"

/**
 * Route function arguments for legacy compatibility
 */
export interface LegacyRouteArgs {
  params?: Record<string, string>
  query?: Record<string, string | number | boolean>
  body?: unknown
  headers?: Record<string, string>
  timeout?: number
  signal?: AbortSignal
}

/**
 * Route function arguments with flattened input
 */
export interface RouteArgs {
  headers?: Record<string, string>
  timeout?: number
  signal?: AbortSignal
}

/**
 * Route function type
 */
export type RouteFunction<T = any> = (args?: RouteArgs) => Promise<T>

/**
 * Route definition for API endpoints (from module system)
 */
export interface RouteDefinition {
  method: HTTPMethod
  path: string
  params?: readonly string[]
  /** Fields that should be sent as query parameters */
  query?: readonly string[]
  /** Fields that should be sent as request body */
  body?: readonly string[]
  requestType?: RequestContentType
  /**
   * If true, the response will be returned as a raw response
   */
  asRaw?: boolean
}

/**
 * Dynamic proxy handler for API routes
 */
export class APIProxyHandler {
  private routes: Map<string, RouteDefinition>
  private client: HttpClient

  constructor(client: HttpClient, routes: Map<string, RouteDefinition>) {
    this.client = client
    this.routes = routes
  }

  /**
   * Proxy get trap that creates route functions dynamically
   */
  get(target: any, propKey: string | symbol): any {
    const routeKey = String(propKey)

    // Handle special properties
    if (routeKey === "constructor" || routeKey === "prototype") {
      return target[propKey]
    }

    // Handle nested routes (e.g., feeds.claim.challenge)
    if (routeKey.includes(".")) {
      return this.handleNestedRoute(routeKey)
    }

    const route = this.routes.get(routeKey)

    if (!route) {
      // For nested routes, return a proxy that continues the chain
      if (this.hasNestedRoute(routeKey)) {
        return this.createNestedProxy(routeKey)
      }

      throw new Error(`Route '${routeKey}' not found`)
    }

    return this.createRouteFunction(route)
  }

  /**
   * Check if a route key has nested routes
   */
  private hasNestedRoute(prefix: string): boolean {
    for (const routeKey of this.routes.keys()) {
      if (routeKey.startsWith(`${prefix}.`)) {
        return true
      }
    }
    return false
  }

  /**
   * Create a nested proxy for route chains
   */
  private createNestedProxy(prefix: string): any {
    const nestedRoutes = new Map<string, RouteDefinition>()

    // Find all routes that start with the prefix
    for (const [routeKey, route] of this.routes.entries()) {
      if (routeKey.startsWith(`${prefix}.`)) {
        const nestedKey = routeKey.slice(Math.max(0, prefix.length + 1))
        nestedRoutes.set(nestedKey, route)
      }
    }

    return new Proxy({}, new APIProxyHandler(this.client, nestedRoutes))
  }

  /**
   * Handle nested route calls
   */
  private handleNestedRoute(routeKey: string): RouteFunction {
    const route = this.routes.get(routeKey)

    if (!route) {
      throw new Error(`Nested route '${routeKey}' not found`)
    }

    return this.createRouteFunction(route)
  }

  /**
   * Create a route function from a route definition
   */
  private createRouteFunction(route: RouteDefinition): RouteFunction {
    return async (
      inputArgs: Record<string, any> | ArrayBuffer | Blob | FormData | undefined = {},
      args: RouteArgs = {},
    ) => {
      const { headers, timeout, signal } = args

      const params: Record<string, string> = {}
      const query: Record<string, string | number | boolean> = {}
      let body: unknown

      // If the first argument is a raw body (FormData / ArrayBuffer / Blob),
      // pass it through directly to preserve the payload (e.g. file uploads, binary data)
      const isFormData =
        typeof FormData !== "undefined" && inputArgs instanceof FormData
      const isArrayBuffer =
        typeof ArrayBuffer !== "undefined" && inputArgs instanceof ArrayBuffer
      const isBlob = typeof Blob !== "undefined" && inputArgs instanceof Blob

      if (isFormData || isArrayBuffer || isBlob) {
        body = inputArgs as BodyInit
      } else {
        // Ensure we always work with an object for extraction logic
        const normalizedArgs: Record<string, any> =
          inputArgs && typeof inputArgs === "object" ?
              (inputArgs as Record<string, any>) :
              {}

        // Extract parameters from flattenedArgs based on route definition
        if (route.params) {
          route.params.forEach((param) => {
            if (normalizedArgs[param] !== undefined) {
              params[param] = String(normalizedArgs[param])
              // Remove param from flattenedArgs to avoid duplication
              delete normalizedArgs[param]
            }
          })
        }

        // Extract query parameters based on route definition
        if (route.query) {
          route.query.forEach((field) => {
            if (normalizedArgs[field] !== undefined) {
              query[field] = normalizedArgs[field]
              // Remove from flattenedArgs to avoid duplication
              delete normalizedArgs[field]
            }
          })
        }

        // Extract body parameters based on route definition
        if (route.body) {
          const bodyData: Record<string, any> = {}
          route.body.forEach((field) => {
            if (normalizedArgs[field] !== undefined) {
              bodyData[field] = normalizedArgs[field]
              // Remove from flattenedArgs to avoid duplication
              delete normalizedArgs[field]
            }
          })
          if (Object.keys(bodyData).length > 0) {
            body = bodyData
          }
        }

        // Handle remaining fields with fallback logic (backward compatibility)
        // For GET requests, remaining args go to query
        // For POST/PUT/PATCH/DELETE requests, remaining args go to body
        // Filter out undefined values
        const remainingFields = Object.keys(normalizedArgs).filter(
          (key) => normalizedArgs[key] !== undefined,
        )
        if (remainingFields.length > 0) {
          const remainingData = Object.fromEntries(
            remainingFields.map((key) => [key, normalizedArgs[key]]),
          )

          if (route.method === "GET") {
            Object.assign(query, remainingData)
          } else {
            // For non-GET requests, merge remaining args into body
            if (body && typeof body === "object") {
              Object.assign(body as Record<string, any>, remainingData)
            } else {
              body = remainingData
            }
          }
        }
      }

      // Build URL with parameters
      let url = route.path
      if (route.params) {
        route.params.forEach((param) => {
          const value = params[param]
          if (value !== undefined) {
            url = url.replace(`{${param}}`, encodeURIComponent(value))
          }
        })
      }

      // Build request options
      const requestOptions: RequestOptions = {
        method: route.method,
        headers,
        timeout,
        signal,
        requestType: route.requestType,
        asRaw: route.asRaw,
      }

      // Add query parameters if they exist
      if (Object.keys(query).length > 0) {
        requestOptions.query = query
      }

      // Add body if it exists
      if (body !== undefined) {
        requestOptions.body = body
      }

      return this.client.request(url, requestOptions)
    }
  }
}

/**
 * Create a typed API proxy from a module definition
 */
export function createAPIProxy<T extends Record<string, any>>(
  client: HttpClient,
  module: ModuleDefinition<any>,
): T {
  // Flatten nested routes and apply prefix
  const flatRoutes = RouteResolver.flattenRoutes(module.routes, module.prefix)

  const routeMap = new Map(Object.entries(flatRoutes))
  return new Proxy({}, new APIProxyHandler(client, routeMap)) as T
}

// Legacy functions removed - new module system only
