use blake3::Hasher as Blake3Hasher;
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use serde::{Deserialize, Serialize};

/// Serde support for [u8; 64] (serde derive only supports arrays up to 32 elements)
mod sig_bytes {
    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    pub fn serialize<S: Serializer>(bytes: &[u8; 64], serializer: S) -> Result<S::Ok, S::Error> {
        bytes.as_slice().serialize(serializer)
    }

    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<[u8; 64], D::Error> {
        let v: Vec<u8> = Vec::deserialize(deserializer)?;
        v.try_into().map_err(|v: Vec<u8>| {
            serde::de::Error::custom(format!("expected 64 bytes, got {}", v.len()))
        })
    }
}

pub type DownloadResponse = NetworkResponse<DownloadResult>;
pub type UploadResponse = NetworkResponse<UploadResult>;
pub type DeleteResponse = NetworkResponse<DeleteResult>;
pub type GetStateResponse = NetworkResponse<GetStateResult>;
pub type GetUserSummaryResponse = NetworkResponse<UserSummaryResult>;
pub type RegisterFolderResponse = NetworkResponse<RegisterFolderResult>;
pub type ListFoldersResponse = NetworkResponse<ListFoldersResult>;
pub type UnregisterFolderResponse = NetworkResponse<UnregisterFolderResult>;

#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum NetworkResponse<T> {
    Success(T),
    Conflict(ConflictResponse),
    Error(ErrorResponse),
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ErrorResponse {
    pub error: String,
    pub message: String,
}

/// Conflict response from server
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ConflictResponse {
    pub error: String,
    pub message: String,
    /// Current revision ID on server
    pub current_revision_id: [u8; 32],
    /// Current revision sequence on server
    pub current_revision_seq: u64,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct HealthResponse {
    pub status: String,
    pub version: String,
}

/// Result of downloading a file (metadata only, ciphertext written to file)
#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
pub struct DownloadResult {
    pub revision_seq: u64,
    pub revision_id: [u8; 32],
    pub size_bytes: u64,
}

#[cfg(feature = "python")]
#[pyo3::prelude::pymethods]
impl DownloadResult {
    #[getter]
    fn revision_seq(&self) -> u64 {
        self.revision_seq
    }

    #[getter]
    fn revision_id(&self) -> String {
        hex::encode(self.revision_id)
    }

    #[getter]
    fn size_bytes(&self) -> u64 {
        self.size_bytes
    }
}

/// Response from upload endpoint
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
pub struct UploadResult {
    pub upload_id: String,
    pub timestamp: i64,
    /// New revision ID assigned by server
    pub revision_id: [u8; 32],
}

#[cfg(feature = "python")]
#[pyo3::prelude::pymethods]
impl UploadResult {
    #[getter]
    fn upload_id(&self) -> &str {
        &self.upload_id
    }

    #[getter]
    fn timestamp(&self) -> i64 {
        self.timestamp
    }

    #[getter]
    fn revision_id(&self) -> String {
        hex::encode(self.revision_id)
    }
}

/// Response from upload endpoint for a S3 upload
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct S3UploadResult {
    pub file_id: String,
    pub upload_id: String,
    pub timestamp: i64,
}

/// Response from uploading to Arion
#[derive(Deserialize)]
pub struct ArionUploadResponse {
    pub hash: String,
    pub message: String,
    pub size: u64,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CanUploadRequest {
    #[serde(alias = "user_id")]
    pub ss58_address: String,
    #[serde(default)]
    pub folder_hash: String,
    pub size_bytes: u64,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
/// Response for can_upload endpoint
pub struct CanUploadResponse {
    pub result: bool,
    pub error: Option<String>,
}

/// Delete response
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DeleteResult {
    pub status: String,
    pub file_id: String,
    pub ss58_address: String,
    pub folder_hash: String,
}

/// User storage summary response
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct UserSummaryResult {
    pub ss58_address: String,
    pub total_bytes: u64,
    pub file_count: u64,
    pub created_at: i64,
    pub updated_at: i64,
}

// =============================================================================
// Folder Registry Types
// =============================================================================

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RegisterFolderRequest {
    #[serde(alias = "user_id")]
    pub ss58_address: String,
    pub folder_hash: String,
    pub label: String,
    #[serde(default)]
    pub device_name: Option<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RegisterFolderResult {
    pub status: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RemoteFolderInfo {
    pub label: String,
    pub folder_hash: String,
    pub file_count: u64,
    pub total_bytes: u64,
    pub created_at: i64,
    pub updated_at: i64,
    #[serde(default)]
    pub device_name: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ListFoldersResult {
    pub base_address: String,
    pub folders: Vec<RemoteFolderInfo>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UnregisterFolderRequest {
    #[serde(alias = "user_id")]
    pub ss58_address: String,
    pub folder_hash: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UnregisterFolderResult {
    pub status: String,
    pub files_deleted: u64,
}

/// Query parameters for pagination
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PaginationQuery {
    pub offset: Option<u32>,
    pub limit: Option<u32>,
}

/// Response for get_state endpoint with pagination
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct GetStateResult {
    pub ss58_address: String,
    pub folder_hash: String,
    pub files: Vec<RemoteFileEntry>,
    pub total_count: u64,
    pub has_more: bool,
    pub offset: u32,
    pub limit: u32,
}

/// File entry as stored on the server
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
pub struct RemoteFileEntry {
    pub path_hash: [u8; 32],
    /// Salted hash: BLAKE3(ss58_address || plaintext)
    pub salted_hash: [u8; 32],
    pub size_bytes: u64,
    pub revision_seq: u64,
    pub revision_id: [u8; 32],
    /// Encrypted path (ChaCha20-Poly1305 encrypted relative path)
    #[serde(default)]
    pub encrypted_path: Vec<u8>,
    /// Plaintext file name (just the filename, not the full path) for display during download
    #[serde(default)]
    pub file_name: Option<String>,
    /// Arion hash (BLAKE3 content hash from the storage backend)
    #[serde(default)]
    pub arion_hash: Option<String>,
}

#[cfg(feature = "python")]
#[pyo3::prelude::pymethods]
impl RemoteFileEntry {
    #[getter]
    fn path_hash(&self) -> String {
        hex::encode(self.path_hash)
    }

    #[getter]
    fn salted_hash(&self) -> String {
        hex::encode(self.salted_hash)
    }

    #[getter]
    fn size_bytes(&self) -> u64 {
        self.size_bytes
    }

    #[getter]
    fn revision_seq(&self) -> u64 {
        self.revision_seq
    }

    #[getter]
    fn revision_id(&self) -> String {
        hex::encode(self.revision_id)
    }
}

#[derive(Deserialize, Serialize)]
pub struct Manifest {
    #[serde(alias = "user_id")]
    pub ss58_address: String,
    #[serde(default)]
    pub folder_hash: String,
    pub ciphertext_hash: String,
    pub size_bytes: u64,
    pub timestamp: i64,
    #[serde(with = "sig_bytes")]
    pub signature: [u8; 64],
    pub signing_key: [u8; 32],
    /// Path hash (BLAKE3 of relative path)
    #[serde(default)]
    pub path_hash: [u8; 32],
    /// Salted hash: BLAKE3(ss58_address || plaintext)
    #[serde(default)]
    pub salted_hash: [u8; 32],
    /// Revision sequence number (monotonic, for replay protection)
    #[serde(default)]
    pub revision_seq: u64,
    /// Base revision ID for optimistic concurrency (None for new files)
    #[serde(default)]
    pub base_revision_id: Option<[u8; 32]>,
    /// Encrypted relative path (for path reconstruction on download)
    #[serde(default)]
    pub encrypted_path: Vec<u8>,
    /// Plaintext file name (just the filename, not the full path) for display during download
    #[serde(default)]
    pub file_name: Option<String>,
}

impl Default for Manifest {
    fn default() -> Self {
        Self {
            ss58_address: String::new(),
            folder_hash: String::new(),
            ciphertext_hash: String::new(),
            size_bytes: 0,
            timestamp: 0,
            signature: [0u8; 64],
            signing_key: [0u8; 32],
            path_hash: [0u8; 32],
            salted_hash: [0u8; 32],
            revision_seq: 0,
            base_revision_id: None,
            encrypted_path: Vec::new(),
            file_name: None,
        }
    }
}

impl Manifest {
    /// This needs to be in par with the one in the hcfs-client
    pub fn generate_text(hash: &str) -> String {
        format!(
            "I here by declare that the file with hash {} that i am uploading is in par with the ToS of the provider",
            hash,
        )
    }

    /// Verifies a request against the manifest: signature + ciphertext hash
    pub async fn verify_request(
        &self,
        ciphertext: &[u8],
    ) -> Result<(), Box<dyn std::error::Error>> {
        self.verify_signature()
            .map_err(|e| -> Box<dyn std::error::Error> { e })?;

        tracing::info!(
            signing_key = %hex::encode(self.signing_key),
            "Signature verified successfully"
        );

        // Verify ciphertext hash matches manifest
        let mut hasher = Blake3Hasher::new();
        hasher.update(ciphertext);
        let computed_hash = hasher.finalize().to_hex().to_string();

        if computed_hash != self.ciphertext_hash {
            return Err("Ciphertext hash does not match manifest".into());
        }
        Ok(())
    }

    /// Verifies just the signature without checking ciphertext hash.
    /// Use this when streaming prevents buffering the ciphertext.
    /// Note: This validates that the client signed the ToS acceptance with the claimed
    /// ciphertext hash, but does NOT verify the actual ciphertext matches that hash.
    pub fn verify_signature(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        let verifying_key = VerifyingKey::from_bytes(&self.signing_key)
            .map_err(|e| format!("Invalid signing key: {}", e))?;
        let signature = Signature::from_bytes(&self.signature);
        let manifest_text = Self::generate_text(&self.ciphertext_hash);

        verifying_key
            .verify(manifest_text.as_bytes(), &signature)
            .map_err(|e| format!("Signature verification failed: {}", e))?;

        Ok(())
    }
}
