///| HTTP Client Implementation using mizchi/x

///|
/// OAuth2HttpClientConfig holds configuration for OAuth2HttpClient
pub struct OAuth2HttpClientConfig {
  max_retries : Int // Maximum number of retries (default: 3)
  base_delay_ms : Int // Base delay for exponential backoff in milliseconds (default: 1000)
  custom_headers : HttpHeaders? // Custom headers to add to all requests
  user_agent : String? // User-Agent header
  debug : Bool // Debug output flag (default: false)
}

///|
/// Create default OAuth2HttpClientConfig
pub fn OAuth2HttpClientConfig::default() -> OAuth2HttpClientConfig {
  {
    max_retries: 3,
    base_delay_ms: 1000,
    custom_headers: None,
    user_agent: None,
    debug: false,
  }
}

///|
/// Create OAuth2HttpClientConfig with builder pattern
pub fn OAuth2HttpClientConfig::builder() -> OAuth2HttpClientConfig {
  OAuth2HttpClientConfig::default()
}

///|
/// Set max_retries for OAuth2HttpClientConfig
pub fn OAuth2HttpClientConfig::with_max_retries(
  self : OAuth2HttpClientConfig,
  max_retries : Int,
) -> OAuth2HttpClientConfig {
  { ..self, max_retries, }
}

///|
/// Set base_delay_ms for OAuth2HttpClientConfig
pub fn OAuth2HttpClientConfig::with_base_delay_ms(
  self : OAuth2HttpClientConfig,
  base_delay_ms : Int,
) -> OAuth2HttpClientConfig {
  { ..self, base_delay_ms, }
}

///|
/// Set custom_headers for OAuth2HttpClientConfig
pub fn OAuth2HttpClientConfig::with_custom_headers(
  self : OAuth2HttpClientConfig,
  custom_headers : HttpHeaders,
) -> OAuth2HttpClientConfig {
  { ..self, custom_headers: Some(custom_headers) }
}

///|
/// Set user_agent for OAuth2HttpClientConfig
pub fn OAuth2HttpClientConfig::with_user_agent(
  self : OAuth2HttpClientConfig,
  user_agent : String,
) -> OAuth2HttpClientConfig {
  { ..self, user_agent: Some(user_agent) }
}

///|
/// Set debug for OAuth2HttpClientConfig
pub fn OAuth2HttpClientConfig::with_debug(
  self : OAuth2HttpClientConfig,
  debug : Bool,
) -> OAuth2HttpClientConfig {
  { ..self, debug, }
}

///|
/// OAuth2HttpClient provides HTTP functionality for OAuth2 operations
pub struct OAuth2HttpClient {
  config : OAuth2HttpClientConfig
}

///|
/// Create a new OAuth2HttpClient with default configuration
pub fn OAuth2HttpClient::new() -> OAuth2HttpClient {
  { config: OAuth2HttpClientConfig::default() }
}

///|
/// Create a new OAuth2HttpClient with custom configuration
pub fn OAuth2HttpClient::new_with_config(
  config : OAuth2HttpClientConfig,
) -> OAuth2HttpClient {
  { config, }
}

///|
/// Create a new OAuth2HttpClient with configurable debug output
/// This method is provided for backward compatibility
pub fn OAuth2HttpClient::new_with_debug(debug : Bool) -> OAuth2HttpClient {
  {
    config: OAuth2HttpClientConfig::default()
    |> OAuth2HttpClientConfig::with_debug(debug),
  }
}

///|
/// Convert HttpHeaders to mizchi/x format
fn convert_headers(headers : HttpHeaders) -> Map[String, String] {
  let result : Map[String, String] = {}
  headers.each(fn(key, value) { result[key] = value })
  result
}

///|
/// Merge custom headers and user agent into base headers
/// Custom headers are applied first, then base headers override them
/// This ensures that request-specific headers take precedence
fn merge_headers(
  base : HttpHeaders,
  custom : HttpHeaders?,
  user_agent : String?,
) -> HttpHeaders {
  let result : HttpHeaders = {}

  // Apply custom headers first
  match custom {
    Some(headers) => headers.each(fn(key, value) { result[key] = value })
    None => ()
  }

  // Apply user agent if specified
  match user_agent {
    Some(ua) => result["User-Agent"] = ua
    None => ()
  }

  // Apply base headers (overrides custom headers)
  base.each(fn(key, value) { result[key] = value })

  result
}

///|
/// Calculate exponential backoff delay in milliseconds
/// Formula: base_delay_ms * (2 ^ retry_count)
/// Examples: 1000ms -> 2000ms -> 4000ms -> 8000ms
fn calculate_backoff(retry_count : Int, base_delay_ms : Int) -> Int {
  base_delay_ms * (1 << retry_count) // bit shift for 2^n
}

///|
/// Check if the HTTP response status code is retryable (5xx errors)
fn is_retryable_status(status_code : Int) -> Bool {
  status_code >= 500 && status_code < 600
}

///|
/// Build application/x-www-form-urlencoded body from parameters
pub fn build_form_urlencoded_body(params : Map[String, String]) -> String {
  let parts : Array[String] = []
  params.each(fn(key, value) {
    let encoded_key = url_encode(key)
    let encoded_value = url_encode(value)
    parts.push("\{encoded_key}=\{encoded_value}")
  })
  parts.join("&")
}

///|
/// URL encoding for form data (RFC 3986)
/// Encodes special characters as %XX where XX is the hex value
pub fn url_encode(s : String) -> String {
  let result = StringBuilder::new()
  for i = 0; i < s.length(); i = i + 1 {
    let ch = s[i]
    let code = ch.to_int()
    // Unreserved characters: A-Z, a-z, 0-9, -, _, ., ~
    if (code >= 65 && code <= 90) ||
      (code >= 97 && code <= 122) ||
      (code >= 48 && code <= 57) ||
      code == 45 ||
      code == 95 ||
      code == 46 ||
      code == 126 {
      result.write_char(ch.unsafe_to_char())
    } else {
      // Encode as %XX
      result.write_string("%")
      result.write_string(int_to_hex(code))
    }
  }
  result.to_string()
}

///|
/// Convert integer to 2-digit uppercase hex string
pub fn int_to_hex(n : Int) -> String {
  let hex_chars = "0123456789ABCDEF"
  let high = n / 16 % 16
  let low = n % 16
  let result = StringBuilder::new()
  result.write_char(hex_chars[high].unsafe_to_char())
  result.write_char(hex_chars[low].unsafe_to_char())
  result.to_string()
}

///|
/// Build Basic Authentication header value
pub fn build_basic_auth_header(
  client_id : ClientId,
  client_secret : ClientSecret,
) -> String {
  let credentials = "\{client_id.to_string()}:\{client_secret.to_string()}"
  let encoded = base64_encode(credentials)
  "Basic \{encoded}"
}

///|
/// Base64 encode a string using moonbitlang/x/codec/base64
/// Standard encoding with padding
fn base64_encode(s : String) -> String {
  // Convert String to BytesView for library
  let bytes = string_to_bytes_view(s)
  @base64.encode(bytes, url_safe=false)
}

///|
/// Base64URL encode a string (RFC 4648 Section 5)
/// Uses URL-safe alphabet: replaces + with - and / with _
/// Removes padding (=) characters
pub fn base64url_encode(s : String) -> String {
  // Convert String to BytesView for library
  let bytes = string_to_bytes_view(s)
  // Encode with URL-safe alphabet and padding
  let encoded = @base64.encode(bytes, url_safe=true)
  // Remove padding characters for RFC 4648 Section 5 compliance
  let mut end = encoded.length()
  while end > 0 && encoded[end - 1] == '=' {
    end = end - 1
  }
  // Build result without padding
  let result = StringBuilder::new()
  for i = 0; i < end; i = i + 1 {
    result.write_char(encoded[i].unsafe_to_char())
  }
  result.to_string()
}

///|
/// Convert String to BytesView for base64 encoding
fn string_to_bytes_view(s : String) -> BytesView {
  let fixed_array = FixedArray::make(s.length(), Byte::default())
  for i = 0; i < s.length(); i = i + 1 {
    fixed_array[i] = s[i].to_int().to_byte()
  }
  let bytes = Bytes::from_array(fixed_array[:])
  bytes.op_as_view()
}

///|
/// Parse OAuth2 error from response body
/// Expects JSON format: {"error": "...", "error_description": "..."}
pub fn parse_oauth2_error(body : String) -> OAuth2Error {
  // Simple JSON parsing for OAuth2 error response
  let error_code = extract_json_string_value(body, "error")
  let error_description = extract_json_string_value(body, "error_description")

  match error_code {
    Some(code) => OAuth2Error::from_error_code(code, error_description)
    None => ParseError("Failed to parse error response: \{body}")
  }
}

///|
/// Extract string value from JSON using @json package
fn extract_json_string_value(json : String, key : String) -> String? {
  let parsed = @json.parse(json) catch { _ => return None }

  match parsed {
    Object(map) =>
      match map.get(key) {
        Some(String(s)) => Some(s)
        _ => None
      }
    _ => None
  }
}

///|
/// Internal POST request without retry logic
async fn OAuth2HttpClient::post_internal(
  self : OAuth2HttpClient,
  url : String,
  headers : HttpHeaders,
  body : String,
) -> Result[HttpResponse, OAuth2Error] {
  // Merge custom headers and user agent into request headers
  let merged_headers = merge_headers(
    headers,
    self.config.custom_headers,
    self.config.user_agent,
  )

  // Convert headers to mizchi/x format
  let http_headers = convert_headers(merged_headers)

  // Debug: print request details
  if self.config.debug {
    println("DEBUG http_client POST:")
    println("  URL: \{url}")
    println("  Body length: \{body.length()}")
  }

  // Send POST request using mizchi/x
  let (response, response_body) = @http.post(url, body, headers=http_headers) catch {
    err => return Err(HttpError("HTTP request failed: \{err}"))
  }

  if self.config.debug {
    println("DEBUG after @http.post:")
    println("  Response code: \{response.code}")
  }

  // Convert response to our HttpResponse type
  let response_headers : HttpHeaders = {}
  response.headers.each(fn(key, value) { response_headers[key] = value })

  // Read body text
  let body_text = response_body.text()

  // Debug: print response details
  if self.config.debug {
    println("DEBUG http_client response:")
    println("  Status: \{response.code}")
    println("  Body length: \{body_text.length()}")
    if body_text.length() > 0 {
      let preview_len = if body_text.length() < 100 {
        body_text.length()
      } else {
        100
      }
      println(
        "  Body (first 100 chars): \{body_text[0:preview_len].to_string()}",
      )
    }
  }

  Ok(HttpResponse::new(response.code, response_headers, body_text))
}

///|
/// Send HTTP POST request with retry logic
pub async fn OAuth2HttpClient::post(
  self : OAuth2HttpClient,
  url : String,
  headers : HttpHeaders,
  body : String,
) -> Result[HttpResponse, OAuth2Error] {
  let mut retry_count = 0
  let mut result : Result[HttpResponse, OAuth2Error]? = None

  while result is None {
    match self.post_internal(url, headers, body) {
      Ok(response) =>
        // Retry on 5xx errors
        if is_retryable_status(response.status_code) &&
          retry_count < self.config.max_retries {
          if self.config.debug {
            println(
              "DEBUG retry POST (attempt \{retry_count + 1}/\{self.config.max_retries}): status \{response.status_code}",
            )
          }

          let backoff_ms = calculate_backoff(
            retry_count,
            self.config.base_delay_ms,
          )
          @async.sleep(backoff_ms)
          retry_count = retry_count + 1
        } else {
          result = Some(Ok(response))
        }
      Err(err) => {
        // Do not retry on non-network errors (e.g., OAuth2 errors)
        // Only retry on HttpError which indicates network issues
        let should_retry = match err {
          HttpError(_) => retry_count < self.config.max_retries
          _ => false
        }

        if should_retry {
          if self.config.debug {
            println(
              "DEBUG retry POST (attempt \{retry_count + 1}/\{self.config.max_retries}): \{err}",
            )
          }

          let backoff_ms = calculate_backoff(
            retry_count,
            self.config.base_delay_ms,
          )
          @async.sleep(backoff_ms)
          retry_count = retry_count + 1
        } else {
          result = Some(Err(err))
        }
      }
    }
  }

  result.unwrap()
}

///|
/// Internal GET request without retry logic
async fn OAuth2HttpClient::get_internal(
  self : OAuth2HttpClient,
  url : String,
  headers : HttpHeaders,
) -> Result[HttpResponse, OAuth2Error] {
  // Merge custom headers and user agent into request headers
  let merged_headers = merge_headers(
    headers,
    self.config.custom_headers,
    self.config.user_agent,
  )

  // Convert headers to mizchi/x format
  let http_headers = convert_headers(merged_headers)

  // Debug: print request details
  if self.config.debug {
    println("DEBUG http_client GET:")
    println("  URL: \{url}")
  }

  // Send GET request using mizchi/x
  let (response, response_body) = @http.get(url, headers=http_headers) catch {
    err => return Err(HttpError("HTTP request failed: \{err}"))
  }

  if self.config.debug {
    println("DEBUG after @http.get:")
    println("  Response code: \{response.code}")
  }

  // Convert response to our HttpResponse type
  let response_headers : HttpHeaders = {}
  response.headers.each(fn(key, value) { response_headers[key] = value })

  // Read body text
  let body_text = response_body.text()

  // Debug: print response details
  if self.config.debug {
    println("DEBUG http_client response:")
    println("  Status: \{response.code}")
    println("  Body length: \{body_text.length()}")
    if body_text.length() > 0 {
      let preview_len = if body_text.length() < 100 {
        body_text.length()
      } else {
        100
      }
      println(
        "  Body (first 100 chars): \{body_text[0:preview_len].to_string()}",
      )
    }
  }

  Ok(HttpResponse::new(response.code, response_headers, body_text))
}

///|
/// Send HTTP GET request with retry logic
/// Used for OIDC UserInfo Endpoint and other GET requests
pub async fn OAuth2HttpClient::get(
  self : OAuth2HttpClient,
  url : String,
  headers : HttpHeaders,
) -> Result[HttpResponse, OAuth2Error] {
  let mut retry_count = 0
  let mut result : Result[HttpResponse, OAuth2Error]? = None

  while result is None {
    match self.get_internal(url, headers) {
      Ok(response) =>
        // Retry on 5xx errors
        if is_retryable_status(response.status_code) &&
          retry_count < self.config.max_retries {
          if self.config.debug {
            println(
              "DEBUG retry GET (attempt \{retry_count + 1}/\{self.config.max_retries}): status \{response.status_code}",
            )
          }

          let backoff_ms = calculate_backoff(
            retry_count,
            self.config.base_delay_ms,
          )
          @async.sleep(backoff_ms)
          retry_count = retry_count + 1
        } else {
          result = Some(Ok(response))
        }
      Err(err) => {
        // Do not retry on non-network errors (e.g., OAuth2 errors)
        // Only retry on HttpError which indicates network issues
        let should_retry = match err {
          HttpError(_) => retry_count < self.config.max_retries
          _ => false
        }

        if should_retry {
          if self.config.debug {
            println(
              "DEBUG retry GET (attempt \{retry_count + 1}/\{self.config.max_retries}): \{err}",
            )
          }

          let backoff_ms = calculate_backoff(
            retry_count,
            self.config.base_delay_ms,
          )
          @async.sleep(backoff_ms)
          retry_count = retry_count + 1
        } else {
          result = Some(Err(err))
        }
      }
    }
  }

  result.unwrap()
}

///|
/// Build scope string from array of Scope
/// Joins scope values with space separator
pub fn build_scope_string(scopes : Array[Scope]) -> String {
  let scope_strs : Array[String] = []
  for i = 0; i < scopes.length(); i = i + 1 {
    scope_strs.push(scopes[i].to_string())
  }
  scope_strs.join(" ")
}