import Std.Internal.Http.Data.Headers

open Std.Http
open Std.Http.Header

/-! ## Header.Name tests -/

-- Valid header names
#guard (Name.ofString? "content-type").isSome
#guard (Name.ofString? "host").isSome
#guard (Name.ofString? "x-custom-header").isSome
#guard (Name.ofString? "accept").isSome

-- Trimming: leading/trailing whitespace is stripped
#guard (Name.ofString? " content-type ").isSome
#guard (Name.ofString? " content-type ") == Name.ofString? "content-type"
#guard (Name.ofString? "  host  ").isSome

-- Invalid header names (empty, spaces, control chars)
#guard (Name.ofString? "").isNone
#guard (Name.ofString? "   ").isNone
#guard (Name.ofString? "invalid header").isNone
#guard (Name.ofString? "bad\nname").isNone
#guard (Name.ofString? "bad\x00name").isNone
#guard (Name.ofString? "bad(name").isNone
#guard (Name.ofString? "bad)name").isNone
#guard (Name.ofString? "bad,name").isNone
#guard (Name.ofString? "bad;name").isNone
#guard (Name.ofString? "bad[name").isNone
#guard (Name.ofString? "bad]name").isNone
#guard (Name.ofString? "bad{name").isNone
#guard (Name.ofString? "bad}name").isNone
#guard (Name.ofString? "bad\"name").isNone

-- Case normalization
/--
info: "content-type"
-/
#guard_msgs in
#eval (Name.ofString! "Content-Type").value

/--
info: "content-type"
-/
#guard_msgs in
#eval (Name.ofString! "CONTENT-TYPE").value

-- Canonical form
/--
info: "Content-Type"
-/
#guard_msgs in
#eval toString (Name.ofString! "content-type")

/--
info: "X-Custom-Header"
-/
#guard_msgs in
#eval toString (Name.ofString! "x-custom-header")

/--
info: "Host"
-/
#guard_msgs in
#eval toString (Name.ofString! "host")

-- Name.is case-insensitive comparison
#guard (Name.ofString! "content-type").is "Content-Type"
#guard (Name.ofString! "content-type").is "CONTENT-TYPE"
#guard (Name.ofString! "content-type").is "content-type"
#guard !(Name.ofString! "content-type").is "host"

-- Predefined names
#guard Name.contentType.value == "content-type"
#guard Name.contentLength.value == "content-length"
#guard Name.host.value == "host"
#guard Name.authorization.value == "authorization"
#guard Name.userAgent.value == "user-agent"
#guard Name.accept.value == "accept"
#guard Name.connection.value == "connection"
#guard Name.transferEncoding.value == "transfer-encoding"
#guard Name.server.value == "server"

-- Name equality
#guard Name.ofString! "content-type" == Name.ofString! "Content-Type"
#guard Name.ofString! "HOST" == Name.ofString! "host"
#guard !(Name.ofString! "content-type" == Name.ofString! "host")

/-! ## Header.Value tests -/

-- Trimming: leading/trailing whitespace is stripped
#guard (Value.ofString? " text/html ") == Value.ofString? "text/html"
#guard (Value.ofString? "  value  ") == Value.ofString? "value"

-- Valid header values (printable ASCII, tab, space)
#guard (Value.ofString? "text/html").isSome
#guard (Value.ofString? "application/json; charset=utf-8").isSome
#guard (Value.ofString? "").isSome
#guard (Value.ofString? "value with spaces").isSome
#guard (Value.ofString? "value\twith\ttabs").isSome

-- Invalid header values (control characters except tab)
#guard (Value.ofString? "bad\x00value").isNone
#guard (Value.ofString? "bad\nvalue").isNone
#guard (Value.ofString? "bad\rvalue").isNone

-- Value.is case-insensitive comparison
#guard (Value.ofString! "text/html").is "TEXT/HTML"
#guard (Value.ofString! "text/html").is "text/html"
#guard !(Value.ofString! "text/html").is "application/json"

-- Value toString
/--
info: "text/html"
-/
#guard_msgs in
#eval toString (Value.ofString! "text/html")

/-! ## Headers collection tests -/

-- Empty headers
#guard Headers.empty.isEmpty
#guard Headers.empty.size == 0

-- Add and retrieve
#guard (Headers.empty.insert! "content-type" "text/html").size == 1
#guard !(Headers.empty.insert! "content-type" "text/html").isEmpty
#guard (Headers.empty.insert! "content-type" "text/html").contains (Name.ofString! "content-type")
#guard (Headers.empty.insert? "content-type" "text/html").isSome
#guard (Headers.empty.insert? "bad header name" "text/html").isNone
#guard (Headers.empty.insert? "content-type" "bad\nvalue").isNone

-- get? retrieves the value
/--
info: "text/html"
-/
#guard_msgs in
#eval do
  let h := Headers.empty.insert! "content-type" "text/html"
  return (h.get? (Name.ofString! "content-type")).get!.value

-- get? returns none for missing headers
#guard (Headers.empty.get? (Name.ofString! "content-type")).isNone

-- Multiple headers
#guard
  let h := Headers.empty
    |>.insert! "content-type" "text/html"
    |>.insert! "host" "example.com"
    |>.insert! "accept" "application/json"
  h.size == 3

#guard
  let h := Headers.empty.insert! "host" "example.com"
  h.contains (Name.ofString! "host") && !h.contains (Name.ofString! "accept")

#guard
  let h := Headers.empty
    |>.insert! "content-type" "text/html"
    |>.insert! "host" "example.com"
  let h' := h.erase (Name.ofString! "content-type")
  !h'.contains (Name.ofString! "content-type") && h'.contains (Name.ofString! "host")

#guard
  let h := Headers.empty
    |>.insert! "content-type" "text/html"
    |>.insert! "host" "example.com"
  (h.erase (Name.ofString! "content-type")).size == 1

-- hasEntry
#guard
  let h := Headers.empty.insert! "content-type" "text/html"
  h.hasEntry (Name.ofString! "content-type") (Value.ofString! "text/html")

#guard
  let h := Headers.empty.insert! "content-type" "text/html"
  !h.hasEntry (Name.ofString! "content-type") (Value.ofString! "application/json")

-- update existing
/--
info: "TEXT/HTML"
-/
#guard_msgs in
#eval do
  let h := Headers.empty.insert! "content-type" "text/html"
  let h' := h.update (Name.ofString! "content-type") (fun v => Value.ofString! v.value.toUpper)
  return (h'.get? (Name.ofString! "content-type")).get!.value

-- ofList
#guard
  let h := Headers.ofList [
    (Name.ofString! "host", Value.ofString! "example.com"),
    (Name.ofString! "accept", Value.ofString! "*/*")
  ]
  h.size == 2 && h.contains (Name.ofString! "host")

-- merge
#guard
  let h1 := Headers.empty.insert! "content-type" "text/html"
  let h2 := Headers.empty.insert! "host" "example.com"
  let merged := h1.merge h2
  merged.contains (Name.ofString! "content-type") && merged.contains (Name.ofString! "host")

-- filter
#guard
  let h := Headers.empty
    |>.insert! "content-type" "text/html"
    |>.insert! "host" "example.com"
    |>.insert! "accept" "application/json"
  let filtered := h.filter (fun name _ => name.is "host")
  filtered.size == 1 && filtered.contains (Name.ofString! "host")

-- fold
/--
info: 3
-/
#guard_msgs in
#eval do
  let h := Headers.empty
    |>.insert! "a" "1"
    |>.insert! "b" "2"
    |>.insert! "c" "3"
  return h.fold 0 (fun acc _ _ => acc + 1)

-- getD with default
/--
info: "fallback"
-/
#guard_msgs in
#eval do
  let h := Headers.empty
  return (h.getD (Name.ofString! "missing") (Value.ofString! "fallback")).value

-- mapValues
/--
info: "TEXT/HTML"
-/
#guard_msgs in
#eval do
  let h := Headers.empty.insert! "content-type" "text/html"
  let h' := h.mapValues (fun _ v => Value.ofString! v.value.toUpper)
  return (h'.get? (Name.ofString! "content-type")).get!.value

-- toArray preserves insertion order
#guard
  let h := Headers.empty
    |>.insert! "content-type" "text/html"
    |>.insert! "host" "example.com"
    |>.insert! "accept" "application/json"
  h.toArray.map (fun (n, v) => (n.value, v.value)) ==
    #[("content-type", "text/html"), ("host", "example.com"), ("accept", "application/json")]

-- toArray with duplicate keys: both values appear in insertion order
#guard
  let h := Headers.empty
    |>.insert! "accept" "text/html"
    |>.insert! "accept" "application/json"
  h.toArray.map (fun (n, v) => (n.value, v.value)) ==
    #[("accept", "text/html"), ("accept", "application/json")]

-- toArray after erase preserves remaining insertion order
#guard
  let h := Headers.empty
    |>.insert! "content-type" "text/html"
    |>.insert! "host" "example.com"
    |>.insert! "accept" "application/json"
  (h.erase (Name.ofString! "host")).toArray.map (fun (n, v) => (n.value, v.value)) ==
    #[("content-type", "text/html"), ("accept", "application/json")]

/-! ## Header typeclass tests -/

-- ContentLength parse
#guard
  match Header.ContentLength.parse (Value.ofString! "42") with
  | some cl => cl.length == 42
  | none => false

#guard
  match Header.ContentLength.parse (Value.ofString! "0") with
  | some cl => cl.length == 0
  | none => false

#guard (Header.ContentLength.parse =<< (Value.ofString! "abc")).isNone
#guard (Header.ContentLength.parse =<< (Value.ofString? "")).isNone

/--
info: ("content-length", "42")
-/
#guard_msgs in
#eval do
  let (name, value) := Header.ContentLength.serialize ⟨42⟩
  return (name.value, value.value)

#guard
  match Header.TransferEncoding.parse (Value.ofString! "chunked") with
  | some te => te.isChunked
  | none => false

#guard
  match Header.TransferEncoding.parse (Value.ofString! "gzip, chunked") with
  | some te => te.isChunked && te.codings.size == 2
  | none => false

#guard
  match Header.TransferEncoding.parse (Value.ofString! "gzip") with
  | some te => !te.isChunked
  | none => false

#guard (Header.TransferEncoding.parse =<< (Value.ofString? "chunked, gzip")).isNone
#guard (Header.TransferEncoding.parse =<< (Value.ofString? "chunked, chunked")).isNone
#guard (Header.TransferEncoding.parse =<< (Value.ofString? "")).isNone
#guard (Header.TransferEncoding.parse =<< (Value.ofString? ",")).isNone
#guard (Header.TransferEncoding.parse =<< (Value.ofString? " , , ")).isNone
#guard (Header.TransferEncoding.parse =<< (Value.ofString? "g zip")).isNone
#guard (Header.TransferEncoding.parse =<< (Value.ofString? "\"chunked\"")).isNone

/--
info: ("transfer-encoding", "gzip,chunked")
-/
#guard_msgs in
#eval do
  let te : Header.TransferEncoding := ⟨#["gzip", "chunked"], by native_decide⟩
  let (name, value) := Header.TransferEncoding.serialize te
  return (name.value, value.value)

#guard
  match Header.Connection.parse (Value.ofString! "keep-alive, Close") with
  | some c => c.containsToken "close" && c.shouldClose
  | none => false

#guard (Header.Connection.parse =<< (Value.ofString? "keep alive")).isNone

/--
info: ("connection", "keep-alive,close")
-/
#guard_msgs in
#eval do
  let c : Header.Connection := ⟨#["keep-alive", "close"], by native_decide⟩
  let (name, value) := Header.Connection.serialize c
  return (name.value, value.value)
