//! Packeta (Zásilkovna) shipment creation via the REST `createPacket` call.
//!
//! See `docs/integrations/packeta.md`. Requires two settings:
//!   - `packeta_api_password` — the secret REST API password (NOT the widget key)
//!   - `packeta_sender_label`  — your sender/eshop label configured in the portal

use loco_rs::prelude::*;

use super::{ShipmentRequest, ShipmentResult};
use crate::shared::settings;

const ENDPOINT: &str = "https://www.zasilkovna.cz/api/rest";

/// Minimal XML-entity escaping for values interpolated into the request body.
fn xml_escape(value: &str) -> String {
    value
        .replace('&', "&amp;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
        .replace('"', "&quot;")
        .replace('\'', "&apos;")
}

/// Extract the text inside the first `<tag>…</tag>` pair, if present.
fn extract(xml: &str, tag: &str) -> Option<String> {
    let open = format!("<{tag}>");
    let close = format!("</{tag}>");
    let start = xml.find(&open)? + open.len();
    let end = xml[start..].find(&close)? + start;
    Some(xml[start..end].trim().to_string())
}

pub async fn create_shipment(ctx: &AppContext, req: ShipmentRequest<'_>) -> Result<ShipmentResult> {
    let api_password = settings::get(ctx, "packeta_api_password").filter(|s| !s.is_empty()).ok_or_else(|| {
        Error::BadRequest(
            "Packeta is not configured: set settings.packeta_api_password (see docs/integrations/packeta.md)".to_string(),
        )
    })?;
    let sender_label = settings::get(ctx, "packeta_sender_label").filter(|s| !s.is_empty()).ok_or_else(|| {
        Error::BadRequest(
            "Packeta is not configured: set settings.packeta_sender_label (see docs/integrations/packeta.md)".to_string(),
        )
    })?;

    // The scaffolded checkout flow delivers to a Packeta pickup point, so an
    // address id is required. (Home delivery uses a different routing flow.)
    let address_id = req.pickup_point_id.filter(|s| !s.is_empty()).ok_or_else(|| {
        Error::BadRequest("this order has no Packeta pickup point selected".to_string())
    })?;

    let value = req.value_cents as f64 / 100.0;
    let cod = req.cod_cents as f64 / 100.0;
    let weight_kg = f64::from(req.weight_grams) / 1000.0;

    let body = format!(
        "<createPacket>\
           <apiPassword>{}</apiPassword>\
           <packetAttributes>\
             <number>{}</number>\
             <name>{}</name>\
             <surname>-</surname>\
             <email>{}</email>\
             <phone>{}</phone>\
             <addressId>{}</addressId>\
             <value>{:.2}</value>\
             <cod>{:.2}</cod>\
             <currency>{}</currency>\
             <weight>{:.3}</weight>\
             <eshop>{}</eshop>\
           </packetAttributes>\
         </createPacket>",
        xml_escape(api_password),
        xml_escape(req.order_number),
        xml_escape(req.recipient_name),
        xml_escape(req.email),
        xml_escape(req.phone.unwrap_or("")),
        xml_escape(address_id),
        value,
        cod,
        "EUR",
        weight_kg,
        xml_escape(sender_label),
    );

    let resp = reqwest::Client::new()
        .post(ENDPOINT)
        .header("Content-Type", "text/xml; charset=utf-8")
        .body(body)
        .send()
        .await
        .map_err(|e| Error::string(&format!("Packeta request failed: {e}")))?;

    let text = resp
        .text()
        .await
        .map_err(|e| Error::string(&format!("Packeta response read failed: {e}")))?;

    // A successful response is <response><status>ok</status><result>…</result>.
    // A failure carries <status>fault</status> plus a <string>/<fault> message.
    if extract(&text, "status").as_deref() != Some("ok") {
        let message = extract(&text, "string")
            .or_else(|| extract(&text, "fault"))
            .unwrap_or_else(|| "unknown Packeta error".to_string());
        return Err(Error::BadRequest(format!("Packeta rejected the shipment: {message}")));
    }

    let shipment_id = extract(&text, "id")
        .ok_or_else(|| Error::string("Packeta response missing packet id"))?;
    let tracking_number = extract(&text, "barcode").unwrap_or_else(|| shipment_id.clone());

    Ok(ShipmentResult {
        label_url: Some(format!("https://tracking.packeta.com/sk/?id={tracking_number}")),
        shipment_id,
        tracking_number,
    })
}
