///| HTTP Client Implementation using mizchi/x

///|
/// OAuth2HttpClient provides HTTP functionality for OAuth2 operations
pub struct OAuth2HttpClient {
  debug : Bool // Debug output flag (default: false)
}

///|
/// Create a new OAuth2HttpClient with debug output disabled (default)
pub fn OAuth2HttpClient::new() -> OAuth2HttpClient {
  { debug: false }
}

///|
/// Create a new OAuth2HttpClient with configurable debug output
pub fn OAuth2HttpClient::new_with_debug(debug : Bool) -> OAuth2HttpClient {
  { 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
}

///|
/// 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
  }
}

///|
/// Send HTTP POST request using mizchi/x
pub async fn OAuth2HttpClient::post(
  self : OAuth2HttpClient,
  url : String,
  headers : HttpHeaders,
  body : String,
) -> Result[HttpResponse, OAuth2Error] {
  // Convert headers to mizchi/x format
  let http_headers = convert_headers(headers)

  // Debug: print request details
  if self.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.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.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
/// Used for OIDC UserInfo Endpoint and other GET requests
pub async fn OAuth2HttpClient::get(
  self : OAuth2HttpClient,
  url : String,
  headers : HttpHeaders,
) -> Result[HttpResponse, OAuth2Error] {
  // Convert headers to mizchi/x format
  let http_headers = convert_headers(headers)

  // Debug: print request details
  if self.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.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.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))
}

///|
/// 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(" ")
}