//! WebDAV sync support for configuration backup/restore.
//!
//! Provides functions to push (upload) and pull (download) the sessions.json
//! config file to/from a WebDAV server, enabling multi-device sync.

use anyhow::{Context, Result};
use reqwest::Client;

/// Test connection to a WebDAV server with the given credentials.
///
/// Sends a PROPFIND request to verify the URL and credentials are valid.
pub async fn test_connection(url: &str, username: &str, password: &str) -> Result<()> {
    let client = build_client()?;
    let url = normalize_url(url);

    let response = client
        .request(reqwest::Method::from_bytes(b"PROPFIND")?, &url)
        .basic_auth(username, Some(password))
        .header("Depth", "0")
        .send()
        .await
        .context("failed to connect to WebDAV server")?;

    if response.status().is_success() || response.status().as_u16() == 207 {
        Ok(())
    } else if response.status().as_u16() == 401 {
        anyhow::bail!("authentication failed: invalid username or password")
    } else if response.status().as_u16() == 404 {
        anyhow::bail!("WebDAV path not found: {}", url)
    } else {
        anyhow::bail!(
            "WebDAV server returned HTTP {}",
            response.status().as_u16()
        )
    }
}

/// Upload (push) config JSON to the WebDAV server.
///
/// The `config_json` should be the raw content of sessions.json.
/// The file is uploaded as `{url}/sessions.json`.
pub async fn push(url: &str, username: &str, password: &str, config_json: &str) -> Result<()> {
    let client = build_client()?;
    let url = format!("{}sessions.json", normalize_url(url));

    let response = client
        .put(&url)
        .basic_auth(username, Some(password))
        .header("Content-Type", "application/json")
        .body(config_json.to_string())
        .send()
        .await
        .context("failed to upload config to WebDAV server")?;

    if response.status().is_success() || response.status().as_u16() == 201 {
        Ok(())
    } else if response.status().as_u16() == 401 {
        anyhow::bail!("authentication failed: invalid username or password")
    } else {
        let status = response.status().as_u16();
        let body = response.text().await.unwrap_or_default();
        anyhow::bail!("WebDAV upload failed (HTTP {status}): {body}")
    }
}

/// Download (pull) config JSON from the WebDAV server.
///
/// Returns the raw JSON content of sessions.json.
pub async fn pull(url: &str, username: &str, password: &str) -> Result<String> {
    let client = build_client()?;
    let url = format!("{}sessions.json", normalize_url(url));

    let response = client
        .get(&url)
        .basic_auth(username, Some(password))
        .send()
        .await
        .context("failed to download config from WebDAV server")?;

    if response.status().is_success() {
        response
            .text()
            .await
            .context("failed to read WebDAV response body")
    } else if response.status().as_u16() == 401 {
        anyhow::bail!("authentication failed: invalid username or password")
    } else if response.status().as_u16() == 404 {
        anyhow::bail!("no config found on WebDAV server (file not found)")
    } else {
        let status = response.status().as_u16();
        anyhow::bail!("WebDAV download failed (HTTP {status})")
    }
}

/// Build an HTTP client with sensible defaults for WebDAV operations.
fn build_client() -> Result<Client> {
    Client::builder()
        .timeout(std::time::Duration::from_secs(30))
        .redirect(reqwest::redirect::Policy::limited(5))
        .build()
        .context("failed to create HTTP client")
}

/// Ensure the URL ends with a single trailing slash.
fn normalize_url(url: &str) -> String {
    let url = url.trim();
    if url.ends_with('/') {
        url.to_string()
    } else {
        format!("{url}/")
    }
}
