// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! FormPost Support for Swift API
//!
//! This module implements HTML form-based file uploads to Swift containers
//! without requiring authentication. FormPost uses HMAC-SHA1 signatures to
//! validate that forms were generated by an authorized user.
//!
//! # Overview
//!
//! FormPost allows users to upload files from HTML forms directly to Swift
//! without exposing authentication credentials to the browser. The container
//! owner generates a signed form with embedded signature, and browsers can
//! POST files to that form.
//!
//! # Configuration
//!
//! FormPost uses the same TempURL key mechanism:
//!
//! ```bash
//! # Set TempURL key for account
//! swift post -m "Temp-URL-Key:mykey"
//! ```
//!
//! # Form Fields
//!
//! Required fields:
//! - `redirect` - URL to redirect to on success
//! - `max_file_size` - Maximum size per file (bytes)
//! - `max_file_count` - Maximum number of files
//! - `expires` - Unix timestamp when form expires
//! - `signature` - HMAC-SHA1 signature of form parameters
//!
//! Optional fields:
//! - `redirect_error` - URL to redirect to on error (default: redirect)
//!
//! File fields:
//! - `file` or `file1`, `file2`, etc. - Files to upload
//!
//! # Signature Generation
//!
//! ```text
//! HMAC-SHA1(key, "{path}\n{redirect}\n{max_file_size}\n{max_file_count}\n{expires}")
//! ```
//!
//! # Example HTML Form
//!
//! ```html
//! <form action="http://swift.example.com/v1/AUTH_account/container"
//!       method="POST" enctype="multipart/form-data">
//!   <input type="hidden" name="redirect" value="https://example.com/success" />
//!   <input type="hidden" name="max_file_size" value="10485760" />
//!   <input type="hidden" name="max_file_count" value="5" />
//!   <input type="hidden" name="expires" value="1640000000" />
//!   <input type="hidden" name="signature" value="abcdef1234567890" />
//!   <input type="file" name="file1" />
//!   <input type="file" name="file2" />
//!   <input type="submit" value="Upload" />
//! </form>
//! ```
//!
//! # Response
//!
//! On success: 303 See Other redirect to `redirect` URL with query params:
//! - `?status=201&message=Created`
//!
//! On error: 303 See Other redirect to `redirect_error` URL (or `redirect`) with:
//! - `?status=400&message=Error+description`

use super::{SwiftError, SwiftResult};
use hmac::{Hmac, KeyInit, Mac};
use sha1::Sha1;
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::debug;

const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_FORMPOST: &str = "swift_formpost";
const EVENT_SWIFT_FORMPOST_STATE: &str = "swift_formpost_state";

type HmacSha1 = Hmac<Sha1>;

/// FormPost request parameters
#[derive(Debug, Clone)]
pub struct FormPostRequest {
    /// URL to redirect to on success
    pub redirect: String,

    /// URL to redirect to on error (defaults to redirect)
    pub redirect_error: Option<String>,

    /// Maximum size per file in bytes
    pub max_file_size: u64,

    /// Maximum number of files
    pub max_file_count: u64,

    /// Unix timestamp when form expires
    pub expires: u64,

    /// HMAC-SHA1 signature
    pub signature: String,
}

impl FormPostRequest {
    /// Parse FormPost parameters from form fields
    pub fn from_form_fields(fields: &std::collections::HashMap<String, String>) -> SwiftResult<Self> {
        // Extract required fields
        let redirect = fields
            .get("redirect")
            .ok_or_else(|| SwiftError::BadRequest("Missing 'redirect' field".to_string()))?
            .clone();

        let max_file_size = fields
            .get("max_file_size")
            .ok_or_else(|| SwiftError::BadRequest("Missing 'max_file_size' field".to_string()))?
            .parse::<u64>()
            .map_err(|_| SwiftError::BadRequest("Invalid 'max_file_size' value".to_string()))?;

        let max_file_count = fields
            .get("max_file_count")
            .ok_or_else(|| SwiftError::BadRequest("Missing 'max_file_count' field".to_string()))?
            .parse::<u64>()
            .map_err(|_| SwiftError::BadRequest("Invalid 'max_file_count' value".to_string()))?;

        let expires = fields
            .get("expires")
            .ok_or_else(|| SwiftError::BadRequest("Missing 'expires' field".to_string()))?
            .parse::<u64>()
            .map_err(|_| SwiftError::BadRequest("Invalid 'expires' value".to_string()))?;

        let signature = fields
            .get("signature")
            .ok_or_else(|| SwiftError::BadRequest("Missing 'signature' field".to_string()))?
            .clone();

        // Optional redirect_error
        let redirect_error = fields.get("redirect_error").cloned();

        Ok(FormPostRequest {
            redirect,
            redirect_error,
            max_file_size,
            max_file_count,
            expires,
            signature,
        })
    }

    /// Get redirect URL for errors (falls back to redirect if redirect_error not set)
    pub fn error_redirect_url(&self) -> &str {
        self.redirect_error.as_deref().unwrap_or(&self.redirect)
    }
}

/// Generate FormPost signature
///
/// Signature format: HMAC-SHA1(key, "{path}\n{redirect}\n{max_file_size}\n{max_file_count}\n{expires}")
pub fn generate_signature(
    path: &str,
    redirect: &str,
    max_file_size: u64,
    max_file_count: u64,
    expires: u64,
    key: &str,
) -> SwiftResult<String> {
    let message = format!("{}\n{}\n{}\n{}\n{}", path, redirect, max_file_size, max_file_count, expires);

    let mut mac =
        HmacSha1::new_from_slice(key.as_bytes()).map_err(|e| SwiftError::InternalServerError(format!("HMAC error: {}", e)))?;
    mac.update(message.as_bytes());

    let result = mac.finalize();
    let signature = hex_simd::encode_to_string(result.into_bytes(), hex_simd::AsciiCase::Lower);

    Ok(signature)
}

/// Validate FormPost signature and expiration
pub fn validate_formpost(path: &str, request: &FormPostRequest, key: &str) -> SwiftResult<()> {
    // Check expiration
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_err(|e| SwiftError::InternalServerError(format!("Time error: {}", e)))?
        .as_secs();

    if now > request.expires {
        return Err(SwiftError::Unauthorized("FormPost expired".to_string()));
    }

    // Validate signature
    let expected_sig = generate_signature(
        path,
        &request.redirect,
        request.max_file_size,
        request.max_file_count,
        request.expires,
        key,
    )?;

    // Compare signatures in constant time to avoid a timing side-channel, matching
    // the sibling TempURL/SFTP checks. Decode the hex first so the comparison runs
    // over the raw HMAC bytes and does not leak via string length; a non-hex
    // provided signature can never match and is rejected the same way.
    let expected_bytes = hex_simd::decode_to_vec(&expected_sig)
        .map_err(|e| SwiftError::InternalServerError(format!("Signature encoding error: {}", e)))?;

    let signatures_match = match hex_simd::decode_to_vec(request.signature.trim()) {
        Ok(provided_bytes) => super::tempurl::constant_time_compare(&provided_bytes, &expected_bytes),
        Err(_) => false,
    };

    if !signatures_match {
        debug!(
            event = EVENT_SWIFT_FORMPOST_STATE,
            component = LOG_COMPONENT_PROTOCOLS,
            subsystem = LOG_SUBSYSTEM_SWIFT_FORMPOST,
            result = "signature_mismatch",
            "swift formpost state changed"
        );
        return Err(SwiftError::Unauthorized("Invalid FormPost signature".to_string()));
    }

    Ok(())
}

/// File uploaded via FormPost
#[derive(Debug)]
pub struct UploadedFile {
    /// Field name (e.g., "file", "file1", "file2")
    pub field_name: String,

    /// Original filename
    pub filename: String,

    /// File contents
    pub contents: Vec<u8>,

    /// Content type
    pub content_type: Option<String>,
}

/// Build redirect URL with status and message
pub fn build_redirect_url(base_url: &str, status: u16, message: &str) -> String {
    let encoded_message = urlencoding::encode(message);
    format!("{}?status={}&message={}", base_url, status, encoded_message)
}

/// Parse multipart/form-data boundary from Content-Type header
pub fn parse_boundary(content_type: &str) -> Option<String> {
    // Content-Type: multipart/form-data; boundary=----WebKitFormBoundary...
    if !content_type.starts_with("multipart/form-data") {
        return None;
    }

    for part in content_type.split(';') {
        let part = part.trim();
        if let Some(boundary) = part.strip_prefix("boundary=") {
            return Some(boundary.to_string());
        }
    }

    None
}

/// Simple multipart form data parser
///
/// This is a basic implementation that extracts form fields and file uploads.
/// For production use, consider using a dedicated multipart library.
pub fn parse_multipart_form(
    body: &[u8],
    boundary: &str,
) -> SwiftResult<(std::collections::HashMap<String, String>, Vec<UploadedFile>)> {
    let mut fields = std::collections::HashMap::new();
    let mut files = Vec::new();

    let boundary_marker = format!("--{}", boundary);
    let body_str = String::from_utf8_lossy(body);

    // Split by boundary
    let parts: Vec<&str> = body_str.split(&boundary_marker).collect();

    for part in parts.iter().skip(1) {
        // Skip empty parts and final boundary
        if part.trim().is_empty() || part.starts_with("--") {
            continue;
        }

        // Split headers from content
        let lines = part.lines();
        let mut headers = Vec::new();
        let mut content_start = 0;

        for (i, line) in lines.clone().enumerate() {
            if line.trim().is_empty() {
                content_start = i + 1;
                break;
            }
            headers.push(line);
        }

        // Parse Content-Disposition header
        let content_disposition = headers
            .iter()
            .find(|h| h.to_lowercase().starts_with("content-disposition:"))
            .map(|h| h.to_string());

        if let Some(disposition) = content_disposition {
            let field_name = extract_field_name(&disposition);
            let filename = extract_filename(&disposition);

            // Get content (everything after headers)
            let content: Vec<&str> = part.lines().skip(content_start).collect();
            let content_str = content.join("\n");
            let content_bytes = content_str.trim_end().as_bytes().to_vec();

            if let Some(fname) = filename {
                // This is a file upload
                let content_type = headers
                    .iter()
                    .find(|h| h.to_lowercase().starts_with("content-type:"))
                    .and_then(|h| h.split(':').nth(1))
                    .map(|s| s.trim().to_string());

                files.push(UploadedFile {
                    field_name: field_name.clone(),
                    filename: fname,
                    contents: content_bytes,
                    content_type,
                });
            } else {
                // This is a regular form field
                fields.insert(field_name, String::from_utf8_lossy(&content_bytes).to_string());
            }
        }
    }

    Ok((fields, files))
}

/// Extract field name from Content-Disposition header
fn extract_field_name(disposition: &str) -> String {
    // Content-Disposition: form-data; name="field_name"
    for part in disposition.split(';') {
        let part = part.trim();
        if let Some(name) = part.strip_prefix("name=\"")
            && let Some(end) = name.find('"')
        {
            return name[..end].to_string();
        }
    }
    String::new()
}

/// Extract filename from Content-Disposition header
fn extract_filename(disposition: &str) -> Option<String> {
    // Content-Disposition: form-data; name="file"; filename="document.pdf"
    for part in disposition.split(';') {
        let part = part.trim();
        if let Some(fname) = part.strip_prefix("filename=\"")
            && let Some(end) = fname.find('"')
        {
            return Some(fname[..end].to_string());
        }
    }
    None
}

/// Handle FormPost upload request
pub async fn handle_formpost(
    account: &str,
    container: &str,
    path: &str,
    content_type: &str,
    body: Vec<u8>,
    tempurl_key: &str,
    credentials: &rustfs_credentials::Credentials,
) -> SwiftResult<http::Response<s3s::Body>> {
    use axum::http::{Response, StatusCode};

    // Parse multipart boundary
    let boundary =
        parse_boundary(content_type).ok_or_else(|| SwiftError::BadRequest("Invalid Content-Type for FormPost".to_string()))?;

    // Parse multipart form
    let (fields, files) = parse_multipart_form(&body, &boundary)?;

    // Parse FormPost request parameters
    let request = FormPostRequest::from_form_fields(&fields)?;

    // Validate signature and expiration
    if let Err(e) = validate_formpost(path, &request, tempurl_key) {
        // Redirect to error URL
        let redirect_url = build_redirect_url(request.error_redirect_url(), 401, &format!("Unauthorized: {}", e));

        return Response::builder()
            .status(StatusCode::SEE_OTHER)
            .header("location", redirect_url)
            .body(s3s::Body::empty())
            .map_err(|e| SwiftError::InternalServerError(format!("Failed to build response: {}", e)));
    }

    // Check file count
    if files.len() as u64 > request.max_file_count {
        let redirect_url = build_redirect_url(
            request.error_redirect_url(),
            400,
            &format!("Too many files: {} > {}", files.len(), request.max_file_count),
        );

        return Response::builder()
            .status(StatusCode::SEE_OTHER)
            .header("location", redirect_url)
            .body(s3s::Body::empty())
            .map_err(|e| SwiftError::InternalServerError(format!("Failed to build response: {}", e)));
    }

    // Upload files
    let mut upload_errors = Vec::new();

    for file in &files {
        // Check file size
        if file.contents.len() as u64 > request.max_file_size {
            upload_errors.push(format!(
                "{}: File too large ({} > {})",
                file.filename,
                file.contents.len(),
                request.max_file_size
            ));
            continue;
        }

        // Upload file to container
        let object_name = &file.filename;
        let reader = std::io::Cursor::new(file.contents.clone());

        // Create headers for upload
        let mut upload_headers = http::HeaderMap::new();
        if let Some(ct) = &file.content_type
            && let Ok(header_value) = http::HeaderValue::from_str(ct)
        {
            upload_headers.insert("content-type", header_value);
        }

        match super::object::put_object(account, container, object_name, credentials, reader, &upload_headers).await {
            Ok(_) => {
                debug!(
                    event = EVENT_SWIFT_FORMPOST_STATE,
                    component = LOG_COMPONENT_PROTOCOLS,
                    subsystem = LOG_SUBSYSTEM_SWIFT_FORMPOST,
                    state = "uploaded",
                    account = %account,
                    container = %container,
                    object = %object_name,
                    "swift formpost state changed"
                );
            }
            Err(e) => {
                upload_errors.push(format!("{}: {}", file.filename, e));
            }
        }
    }

    // Build redirect response
    let (status, message, redirect_url_str) = if upload_errors.is_empty() {
        (201, "Created".to_string(), request.redirect.clone())
    } else {
        (
            400,
            format!("Upload errors: {}", upload_errors.join(", ")),
            request.error_redirect_url().to_string(),
        )
    };

    let redirect_url = build_redirect_url(&redirect_url_str, status, &message);

    Response::builder()
        .status(StatusCode::SEE_OTHER)
        .header("location", redirect_url)
        .body(s3s::Body::empty())
        .map_err(|e| SwiftError::InternalServerError(format!("Failed to build response: {}", e)))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_generate_signature() {
        let sig = generate_signature("/v1/AUTH_test/container", "https://example.com/success", 10485760, 5, 1640000000, "mykey")
            .unwrap();

        // Signature should be consistent
        let sig2 = generate_signature("/v1/AUTH_test/container", "https://example.com/success", 10485760, 5, 1640000000, "mykey")
            .unwrap();

        assert_eq!(sig, sig2);
        assert_eq!(sig.len(), 40); // SHA1 hex is 40 characters
    }

    #[test]
    fn test_signature_path_sensitive() {
        let sig1 = generate_signature(
            "/v1/AUTH_test/container1",
            "https://example.com/success",
            10485760,
            5,
            1640000000,
            "mykey",
        )
        .unwrap();

        let sig2 = generate_signature(
            "/v1/AUTH_test/container2",
            "https://example.com/success",
            10485760,
            5,
            1640000000,
            "mykey",
        )
        .unwrap();

        assert_ne!(sig1, sig2);
    }

    #[test]
    fn test_signature_redirect_sensitive() {
        let sig1 = generate_signature(
            "/v1/AUTH_test/container",
            "https://example.com/success1",
            10485760,
            5,
            1640000000,
            "mykey",
        )
        .unwrap();

        let sig2 = generate_signature(
            "/v1/AUTH_test/container",
            "https://example.com/success2",
            10485760,
            5,
            1640000000,
            "mykey",
        )
        .unwrap();

        assert_ne!(sig1, sig2);
    }

    #[test]
    fn test_signature_max_file_size_sensitive() {
        let sig1 = generate_signature("/v1/AUTH_test/container", "https://example.com/success", 10485760, 5, 1640000000, "mykey")
            .unwrap();

        let sig2 = generate_signature("/v1/AUTH_test/container", "https://example.com/success", 20971520, 5, 1640000000, "mykey")
            .unwrap();

        assert_ne!(sig1, sig2);
    }

    #[test]
    fn test_signature_max_file_count_sensitive() {
        let sig1 = generate_signature("/v1/AUTH_test/container", "https://example.com/success", 10485760, 5, 1640000000, "mykey")
            .unwrap();

        let sig2 = generate_signature(
            "/v1/AUTH_test/container",
            "https://example.com/success",
            10485760,
            10,
            1640000000,
            "mykey",
        )
        .unwrap();

        assert_ne!(sig1, sig2);
    }

    #[test]
    fn test_signature_expires_sensitive() {
        let sig1 = generate_signature("/v1/AUTH_test/container", "https://example.com/success", 10485760, 5, 1640000000, "mykey")
            .unwrap();

        let sig2 = generate_signature("/v1/AUTH_test/container", "https://example.com/success", 10485760, 5, 1740000000, "mykey")
            .unwrap();

        assert_ne!(sig1, sig2);
    }

    #[test]
    fn test_signature_key_sensitive() {
        let sig1 = generate_signature(
            "/v1/AUTH_test/container",
            "https://example.com/success",
            10485760,
            5,
            1640000000,
            "mykey1",
        )
        .unwrap();

        let sig2 = generate_signature(
            "/v1/AUTH_test/container",
            "https://example.com/success",
            10485760,
            5,
            1640000000,
            "mykey2",
        )
        .unwrap();

        assert_ne!(sig1, sig2);
    }

    #[test]
    fn test_validate_formpost_valid() {
        let key = "mykey";
        let path = "/v1/AUTH_test/container";
        let redirect = "https://example.com/success";
        let max_file_size = 10485760;
        let max_file_count = 5;
        let expires = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + 3600; // 1 hour from now

        let signature = generate_signature(path, redirect, max_file_size, max_file_count, expires, key).unwrap();

        let request = FormPostRequest {
            redirect: redirect.to_string(),
            redirect_error: None,
            max_file_size,
            max_file_count,
            expires,
            signature,
        };

        let result = validate_formpost(path, &request, key);
        assert!(result.is_ok());
    }

    #[test]
    fn test_validate_formpost_expired() {
        let key = "mykey";
        let path = "/v1/AUTH_test/container";
        let redirect = "https://example.com/success";
        let max_file_size = 10485760;
        let max_file_count = 5;
        let expires = 1000000000; // Past timestamp

        let signature = generate_signature(path, redirect, max_file_size, max_file_count, expires, key).unwrap();

        let request = FormPostRequest {
            redirect: redirect.to_string(),
            redirect_error: None,
            max_file_size,
            max_file_count,
            expires,
            signature,
        };

        let result = validate_formpost(path, &request, key);
        assert!(result.is_err());
        match result {
            Err(SwiftError::Unauthorized(msg)) => assert!(msg.contains("expired")),
            _ => panic!("Expected Unauthorized error"),
        }
    }

    #[test]
    fn test_validate_formpost_wrong_signature() {
        let key = "mykey";
        let path = "/v1/AUTH_test/container";
        let expires = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + 3600;

        let request = FormPostRequest {
            redirect: "https://example.com/success".to_string(),
            redirect_error: None,
            max_file_size: 10485760,
            max_file_count: 5,
            expires,
            signature: "invalid_signature".to_string(),
        };

        let result = validate_formpost(path, &request, key);
        assert!(result.is_err());
        match result {
            Err(SwiftError::Unauthorized(msg)) => assert!(msg.contains("Invalid")),
            _ => panic!("Expected Unauthorized error"),
        }
    }

    #[test]
    fn test_validate_formpost_wrong_hex_signature() {
        // A well-formed hex signature that does not match the expected one must still
        // be rejected. This exercises the constant-time comparison path (the decoded
        // bytes differ) rather than the hex-decode failure path.
        let key = "mykey";
        let path = "/v1/AUTH_test/container";
        let expires = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + 3600;

        let request = FormPostRequest {
            redirect: "https://example.com/success".to_string(),
            redirect_error: None,
            max_file_size: 10485760,
            max_file_count: 5,
            expires,
            // Valid 40-char hex, but not the correct signature for these params.
            signature: "da39a3ee5e6b4b0d3255bfef95601890afd80709".to_string(),
        };

        let result = validate_formpost(path, &request, key);
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), SwiftError::Unauthorized(_)));
    }

    #[test]
    fn test_build_redirect_url() {
        let url = build_redirect_url("https://example.com/success", 201, "Created");
        assert_eq!(url, "https://example.com/success?status=201&message=Created");

        let url = build_redirect_url("https://example.com/error", 400, "File too large");
        assert_eq!(url, "https://example.com/error?status=400&message=File%20too%20large");
    }

    #[test]
    fn test_formpost_request_error_redirect_url() {
        let request = FormPostRequest {
            redirect: "https://example.com/success".to_string(),
            redirect_error: Some("https://example.com/error".to_string()),
            max_file_size: 10485760,
            max_file_count: 5,
            expires: 1640000000,
            signature: "sig".to_string(),
        };

        assert_eq!(request.error_redirect_url(), "https://example.com/error");

        let request_no_error = FormPostRequest {
            redirect: "https://example.com/success".to_string(),
            redirect_error: None,
            max_file_size: 10485760,
            max_file_count: 5,
            expires: 1640000000,
            signature: "sig".to_string(),
        };

        assert_eq!(request_no_error.error_redirect_url(), "https://example.com/success");
    }

    #[test]
    fn test_from_form_fields_valid() {
        let mut fields = std::collections::HashMap::new();
        fields.insert("redirect".to_string(), "https://example.com/success".to_string());
        fields.insert("max_file_size".to_string(), "10485760".to_string());
        fields.insert("max_file_count".to_string(), "5".to_string());
        fields.insert("expires".to_string(), "1640000000".to_string());
        fields.insert("signature".to_string(), "abcdef".to_string());

        let result = FormPostRequest::from_form_fields(&fields);
        assert!(result.is_ok());

        let request = result.unwrap();
        assert_eq!(request.redirect, "https://example.com/success");
        assert_eq!(request.max_file_size, 10485760);
        assert_eq!(request.max_file_count, 5);
        assert_eq!(request.expires, 1640000000);
        assert_eq!(request.signature, "abcdef");
    }

    #[test]
    fn test_from_form_fields_missing_redirect() {
        let mut fields = std::collections::HashMap::new();
        fields.insert("max_file_size".to_string(), "10485760".to_string());
        fields.insert("max_file_count".to_string(), "5".to_string());
        fields.insert("expires".to_string(), "1640000000".to_string());
        fields.insert("signature".to_string(), "abcdef".to_string());

        let result = FormPostRequest::from_form_fields(&fields);
        assert!(result.is_err());
    }

    #[test]
    fn test_from_form_fields_invalid_max_file_size() {
        let mut fields = std::collections::HashMap::new();
        fields.insert("redirect".to_string(), "https://example.com/success".to_string());
        fields.insert("max_file_size".to_string(), "not_a_number".to_string());
        fields.insert("max_file_count".to_string(), "5".to_string());
        fields.insert("expires".to_string(), "1640000000".to_string());
        fields.insert("signature".to_string(), "abcdef".to_string());

        let result = FormPostRequest::from_form_fields(&fields);
        assert!(result.is_err());
    }
}
