use crate::ffi;
use std::ffi::{CStr, CString};

/// `METHOD_READ` is the method string for read (fetch) operations. It is safe
/// to send on QUIC 0-RTT early data because it is idempotent and has no
/// observable side effects.
pub const METHOD_READ: &str = "read";

/// `METHOD_WRITE` is the method string for creating or replacing a resource.
/// It is not idempotent and must not be sent on 0-RTT early data.
pub const METHOD_WRITE: &str = "write";

/// `METHOD_UPDATE` is the method string for partially modifying an existing
/// resource. It is not idempotent.
pub const METHOD_UPDATE: &str = "update";

/// `METHOD_DELETE` is the method string for removing a resource. It is
/// idempotent (deleting a non-existent resource is a no-op) but is not allowed
/// on 0-RTT because the server must authenticate the caller first.
pub const METHOD_DELETE: &str = "delete";

/// `METHOD_CONNECT` is the method string for the NWEP application-layer
/// handshake message. Every new QUIC connection begins with a `connect`
/// exchange that negotiates capabilities (`max-streams`,
/// `max-message-size`, `compression`) and establishes mutual authentication.
pub const METHOD_CONNECT: &str = "connect";

/// `METHOD_AUTHENTICATE` is the method string for the challenge-response mutual
/// authentication sub-protocol that runs inside the `connect` handshake.
pub const METHOD_AUTHENTICATE: &str = "authenticate";

/// `METHOD_HEARTBEAT` is the method string for keep-alive pings. The server
/// responds with `STATUS_OK` and no body.
pub const METHOD_HEARTBEAT: &str = "heartbeat";

/// `STATUS_OK` is the success status for a request that completed normally and
/// produced a response body (analogous to HTTP 200).
pub const STATUS_OK: &str = "ok";

/// `STATUS_CREATED` is the success status for a request that created a new
/// resource (analogous to HTTP 201). The response may include a `Location`-
/// style header identifying the new resource.
pub const STATUS_CREATED: &str = "created";

/// `STATUS_ACCEPTED` is the success status for a request that was accepted for
/// asynchronous processing but has not yet completed (analogous to HTTP 202).
pub const STATUS_ACCEPTED: &str = "accepted";

/// `STATUS_NO_CONTENT` is the success status for a request that completed
/// successfully but produced no response body (analogous to HTTP 204). Typical
/// for `delete` and `update` operations.
pub const STATUS_NO_CONTENT: &str = "no_content";

/// `STATUS_BAD_REQUEST` is the error status for a request that was malformed or
/// missing required headers (analogous to HTTP 400).
pub const STATUS_BAD_REQUEST: &str = "bad_request";

/// `STATUS_UNAUTHORIZED` is the error status for a request from a caller that
/// has not authenticated or whose credentials are invalid (analogous to HTTP
/// 401).
pub const STATUS_UNAUTHORIZED: &str = "unauthorized";

/// `STATUS_FORBIDDEN` is the error status for a request from an authenticated
/// caller who lacks the permission to perform the operation (analogous to HTTP
/// 403).
pub const STATUS_FORBIDDEN: &str = "forbidden";

/// `STATUS_NOT_FOUND` is the error status for a request targeting a resource or
/// path that does not exist (analogous to HTTP 404).
pub const STATUS_NOT_FOUND: &str = "not_found";

/// `STATUS_CONFLICT` is the error status for a request that conflicts with the
/// current state (e.g., duplicate key registration, concurrent modification;
/// analogous to HTTP 409).
pub const STATUS_CONFLICT: &str = "conflict";

/// `STATUS_RATE_LIMITED` is the error status returned when the caller has
/// exceeded the server's request rate limit. Callers should back off and retry
/// after the delay indicated in the `retry-after` header.
pub const STATUS_RATE_LIMITED: &str = "rate_limited";

/// `STATUS_INTERNAL_ERROR` is the error status for an unexpected server-side
/// failure (analogous to HTTP 500).
pub const STATUS_INTERNAL_ERROR: &str = "internal_error";

/// `STATUS_UNAVAILABLE` is the error status for a transient server-side failure
/// where the caller may retry (analogous to HTTP 503).
pub const STATUS_UNAVAILABLE: &str = "unavailable";

/// `HDR_METHOD` is the `:method` pseudo-header name. It carries the method
/// string (e.g., `"read"`) and must be the first header in every request
/// message.
pub const HDR_METHOD: &str = ":method";

/// `HDR_PATH` is the `:path` pseudo-header name. It carries the request path
/// (e.g., `"/objects/abc123"`) and must appear in every request after
/// `:method`.
pub const HDR_PATH: &str = ":path";

/// `HDR_VERSION` is the `:version` pseudo-header name. It carries the protocol
/// version string (see [`crate::types::PROTO_VER`]) and is sent in `connect`
/// requests to allow version negotiation.
pub const HDR_VERSION: &str = ":version";

/// `HDR_STATUS` is the `:status` pseudo-header name. It carries the response
/// status string (e.g., `"ok"`, `"not_found"`) and must be the first header in
/// every response message.
pub const HDR_STATUS: &str = ":status";

/// `HDR_REQUEST_ID` is the `request-id` header name. It carries a
/// 16-byte random ID (hex-encoded) that uniquely identifies a single
/// request/response exchange, enabling log correlation.
pub const HDR_REQUEST_ID: &str = "request-id";

/// `HDR_CLIENT_ID` is the `client-id` header name. It carries the hex-encoded
/// [`crate::types::NodeId`] of the connecting client, sent in the `connect`
/// request.
pub const HDR_CLIENT_ID: &str = "client-id";

/// `HDR_SERVER_ID` is the `server-id` header name. It carries the hex-encoded
/// [`crate::types::NodeId`] of the server, returned in the `connect` response.
pub const HDR_SERVER_ID: &str = "server-id";

/// `HDR_CHALLENGE` is the `challenge` header name. It carries a random nonce
/// (see [`crate::types::CHALLENGE_LEN`]) sent by the server during the
/// `authenticate` sub-protocol, which the client must sign to prove key
/// possession.
pub const HDR_CHALLENGE: &str = "challenge";

/// `HDR_CHALLENGE_RESPONSE` is the `challenge-response` header name. It carries
/// the client's Ed25519 signature over the server's challenge nonce.
pub const HDR_CHALLENGE_RESPONSE: &str = "challenge-response";

/// `HDR_SERVER_CHALLENGE` is the `server-challenge` header name. It carries the
/// server-side challenge nonce sent to the client during mutual authentication.
pub const HDR_SERVER_CHALLENGE: &str = "server-challenge";

/// `HDR_AUTH_RESPONSE` is the `auth-response` header name. It carries the
/// client's response to the server challenge during mutual authentication.
pub const HDR_AUTH_RESPONSE: &str = "auth-response";

/// `HDR_MAX_STREAMS` is the `max-streams` header name. Both peers advertise
/// their maximum concurrent stream count during `connect` negotiation; the
/// minimum of the two values is used.
pub const HDR_MAX_STREAMS: &str = "max-streams";

/// `HDR_MAX_MESSAGE_SIZE` is the `max-message-size` header name. Both peers
/// advertise their maximum message body size during `connect`; the minimum is
/// used for the session.
pub const HDR_MAX_MESSAGE_SIZE: &str = "max-message-size";

/// `HDR_COMPRESSION` is the `compression` header name. It advertises the
/// compression algorithms the peer supports (e.g., `"zstd"`). An empty or
/// absent value means no compression.
pub const HDR_COMPRESSION: &str = "compression";

/// `HDR_ROLES` is the `roles` header name. The server advertises its
/// [`crate::role::ServerRole`] string (e.g., `"log_server"`) in the `connect`
/// response so clients can discover server capabilities.
pub const HDR_ROLES: &str = "roles";

/// `HDR_TRANSCRIPT_SIG` is the `transcript-signature` header name. It carries
/// each peer's Ed25519 signature over the complete handshake transcript,
/// providing mutual authentication and binding the TLS session to the NWEP
/// identity layer.
pub const HDR_TRANSCRIPT_SIG: &str = "transcript-signature";

/// `HDR_STATUS_DETAILS` is the `status-details` header name. It carries a
/// human-readable or machine-parseable string that provides additional context
/// for a non-`ok` status code.
pub const HDR_STATUS_DETAILS: &str = "status-details";

/// `HDR_RETRY_AFTER` is the `retry-after` header name. When the server returns
/// [`STATUS_RATE_LIMITED`] or [`STATUS_UNAVAILABLE`], this header contains a
/// duration (in seconds) after which the client should retry.
pub const HDR_RETRY_AFTER: &str = "retry-after";

/// `HDR_TRACE_ID` is the `trace-id` header name. It carries a 16-byte random
/// distributed trace ID (hex-encoded) that propagates across all hops of a
/// multi-server request chain, enabling end-to-end tracing.
pub const HDR_TRACE_ID: &str = "trace-id";

/// `HDR_EVENT` is the `:event` pseudo-header name. It identifies the event type
/// in server-initiated notification messages (`MSG_NOTIFY`).
pub const HDR_EVENT: &str = ":event";

/// `HDR_NOTIFY_ID` is the `notify-id` header name. It carries a 16-byte random
/// ID that uniquely identifies a server-push notification, enabling
/// deduplication on the client side.
pub const HDR_NOTIFY_ID: &str = "notify-id";

/// `method_is_valid` returns `true` if `method` is a recognized NWEP method
/// name.
///
/// The check is delegated to the C library, which accepts exactly the seven
/// method strings defined as `METHOD_*` constants in this module.
///
/// # Example
///
/// ```rust
/// use nwep::protocol::{method_is_valid, METHOD_READ};
///
/// assert!(method_is_valid(METHOD_READ));
/// assert!(!method_is_valid("GET"));
/// ```
pub fn method_is_valid(method: &str) -> bool {
    let c = CString::new(method).unwrap_or_default();
    unsafe { ffi::nwep_method_is_valid(c.as_ptr()) != 0 }
}

/// `method_is_idempotent` returns `true` if repeating `method` produces the
/// same result as calling it once.
///
/// Currently `"read"` and `"delete"` are considered idempotent. Idempotent
/// methods are safe to retry automatically after a transient network failure.
///
/// # Example
///
/// ```rust
/// use nwep::protocol::method_is_idempotent;
///
/// assert!(method_is_idempotent("read"));
/// assert!(method_is_idempotent("delete"));
/// assert!(!method_is_idempotent("write"));
/// ```
pub fn method_is_idempotent(method: &str) -> bool {
    let c = CString::new(method).unwrap_or_default();
    unsafe { ffi::nwep_method_is_idempotent(c.as_ptr()) != 0 }
}

/// `method_allowed_0rtt` returns `true` if `method` is safe to send in QUIC
/// 0-RTT (early data) before the handshake completes.
///
/// Only `"read"` qualifies: it is both idempotent and carries no side effects,
/// so replaying it in the event of a 0-RTT rejection is harmless. All other
/// methods must wait for the full QUIC handshake.
///
/// # Example
///
/// ```rust
/// use nwep::protocol::method_allowed_0rtt;
///
/// assert!(method_allowed_0rtt("read"));
/// assert!(!method_allowed_0rtt("write"));
/// assert!(!method_allowed_0rtt("delete"));
/// ```
pub fn method_allowed_0rtt(method: &str) -> bool {
    let c = CString::new(method).unwrap_or_default();
    unsafe { ffi::nwep_method_allowed_0rtt(c.as_ptr()) != 0 }
}

/// `status_is_valid` returns `true` if `status` is a recognized NWEP status
/// string.
///
/// # Example
///
/// ```rust
/// use nwep::protocol::status_is_valid;
///
/// assert!(status_is_valid("ok"));
/// assert!(!status_is_valid("200"));
/// ```
pub fn status_is_valid(status: &str) -> bool {
    let c = CString::new(status).unwrap_or_default();
    unsafe { ffi::nwep_status_is_valid(c.as_ptr()) != 0 }
}

/// `status_is_success` returns `true` if `status` indicates a successful
/// outcome.
///
/// The following statuses are considered successful: `"ok"`, `"created"`,
/// `"accepted"`, and `"no_content"`. All other recognized statuses are
/// considered errors.
///
/// # Example
///
/// ```rust
/// use nwep::protocol::status_is_success;
///
/// assert!(status_is_success("ok"));
/// assert!(status_is_success("created"));
/// assert!(status_is_success("accepted"));
/// assert!(status_is_success("no_content"));
/// assert!(!status_is_success("not_found"));
/// ```
pub fn status_is_success(status: &str) -> bool {
    let c = CString::new(status).unwrap_or_default();
    unsafe { ffi::nwep_status_is_success(c.as_ptr()) != 0 }
}

/// `status_is_error` returns `true` if `status` indicates a failure outcome.
///
/// This is the logical complement of [`status_is_success`] for recognized
/// status strings. Unrecognized strings return `false` from both functions.
///
/// # Example
///
/// ```rust
/// use nwep::protocol::status_is_error;
///
/// assert!(status_is_error("not_found"));
/// assert!(status_is_error("internal_error"));
/// assert!(!status_is_error("ok"));
/// ```
pub fn status_is_error(status: &str) -> bool {
    let c = CString::new(status).unwrap_or_default();
    unsafe { ffi::nwep_status_is_error(c.as_ptr()) != 0 }
}

/// `trace_id_generate` generates a cryptographically random 16-byte trace ID.
///
/// Trace IDs propagate through multi-hop request chains via the [`HDR_TRACE_ID`]
/// header, enabling distributed tracing. Each new outbound request chain should
/// generate a fresh trace ID. The bytes are sourced from OpenSSL's RAND, so the
/// function may fail if the entropy pool is not yet seeded.
///
/// # Errors
///
/// Returns [`crate::Error`] if the C library's random number generation fails.
///
/// # Example
///
/// ```rust
/// let tid = nwep::protocol::trace_id_generate().unwrap();
/// assert_eq!(tid.len(), 16);
/// ```
pub fn trace_id_generate() -> Result<[u8; 16], crate::Error> {
    let mut id = [0u8; 16];
    crate::error::check(unsafe { ffi::nwep_trace_id_generate(id.as_mut_ptr()) })?;
    Ok(id)
}

/// `request_id_generate` generates a cryptographically random 16-byte request
/// ID.
///
/// Request IDs are attached to individual request/response pairs via the
/// [`HDR_REQUEST_ID`] header and allow log entries from the client and server
/// to be correlated for a single RPC. Unlike trace IDs, request IDs are
/// generated per-request rather than per-chain.
///
/// # Errors
///
/// Returns [`crate::Error`] if the C library's random number generation fails.
///
/// # Example
///
/// ```rust
/// let rid = nwep::protocol::request_id_generate().unwrap();
/// assert_eq!(rid.len(), 16);
/// ```
pub fn request_id_generate() -> Result<[u8; 16], crate::Error> {
    let mut id = [0u8; 16];
    crate::error::check(unsafe { ffi::nwep_request_id_generate(id.as_mut_ptr()) })?;
    Ok(id)
}

/// `err_to_status` maps a numeric NWEP error code to its corresponding protocol
/// status string.
///
/// This is a standalone function equivalent to [`crate::Error::to_status`] that
/// works without constructing a full [`crate::Error`] value. It is useful in
/// server handlers that already hold an error code and need to produce a
/// response status header.
///
/// Returns `"internal_error"` if `code` has no mapping or the C library returns
/// a null pointer.
///
/// # Example
///
/// ```rust
/// use nwep::protocol::err_to_status;
/// use nwep::error::ERR_NETWORK_CONN_FAILED;
///
/// assert_eq!(err_to_status(ERR_NETWORK_CONN_FAILED), "internal_error");
/// ```
pub fn err_to_status(code: i32) -> &'static str {
    unsafe {
        let ptr = ffi::nwep_err_to_status(code);
        if ptr.is_null() {
            "internal_error"
        } else {
            CStr::from_ptr(ptr).to_str().unwrap_or("internal_error")
        }
    }
}

/// `put_uint32be` writes `n` as a 4-byte big-endian integer into the first 4
/// bytes of `buf`.
///
/// This mirrors the C library's `nwep_put_uint32be` and is used when manually
/// constructing NWEP frame headers.
///
/// # Panics
///
/// Panics if `buf.len() < 4`.
pub fn put_uint32be(buf: &mut [u8], n: u32) {
    assert!(buf.len() >= 4);
    unsafe { ffi::nwep_put_uint32be(buf.as_mut_ptr(), n) };
}

/// `get_uint32be` reads a 4-byte big-endian integer from the first 4 bytes of
/// `buf` and returns it as a `u32`.
///
/// This mirrors the C library's `nwep_get_uint32be` and is used when parsing
/// NWEP frame headers.
///
/// # Panics
///
/// Panics if `buf.len() < 4`.
pub fn get_uint32be(buf: &[u8]) -> u32 {
    assert!(buf.len() >= 4);
    let mut n = 0u32;
    unsafe { ffi::nwep_get_uint32be(&mut n, buf.as_ptr()) };
    n
}

/// `put_uint16be` writes `n` as a 2-byte big-endian integer into the first 2
/// bytes of `buf`.
///
/// # Panics
///
/// Panics if `buf.len() < 2`.
pub fn put_uint16be(buf: &mut [u8], n: u16) {
    assert!(buf.len() >= 2);
    unsafe { ffi::nwep_put_uint16be(buf.as_mut_ptr(), n) };
}

/// `get_uint16be` reads a 2-byte big-endian integer from the first 2 bytes of
/// `buf` and returns it as a `u16`.
///
/// # Panics
///
/// Panics if `buf.len() < 2`.
pub fn get_uint16be(buf: &[u8]) -> u16 {
    assert!(buf.len() >= 2);
    let mut n = 0u16;
    unsafe { ffi::nwep_get_uint16be(&mut n, buf.as_ptr()) };
    n
}
