//! Transaction JSON Serialization.
//!
//! Provides JSON serialization and deserialization for Transaction, TransactionInput,
//! and TransactionOutput, matching the Go SDK's JSON format for cross-SDK compatibility.
//!
//! The JSON format uses the following field names:
//! - Transaction: `txid`, `hex`, `inputs`, `outputs`, `version`, `lockTime`
//! - Input: `unlockingScript`, `txid`, `vout`, `sequence`
//! - Output: `satoshis`, `lockingScript`

use serde::{Deserialize, Serialize};

use super::input::TransactionInput;
use super::output::TransactionOutput;
use super::transaction::Transaction;
use crate::script::{LockingScript, UnlockingScript};
use crate::Result;

/// JSON representation of a Transaction, matching Go SDK's txJSON struct.
#[derive(Serialize, Deserialize)]
struct TxJson {
    txid: String,
    hex: String,
    inputs: Vec<InputJson>,
    outputs: Vec<OutputJson>,
    version: u32,
    #[serde(rename = "lockTime")]
    lock_time: u32,
}

/// JSON representation of a TransactionInput, matching Go SDK's inputJSON struct.
#[derive(Serialize, Deserialize)]
struct InputJson {
    #[serde(rename = "unlockingScript")]
    unlocking_script: String,
    txid: String,
    vout: u32,
    sequence: u32,
}

/// JSON representation of a TransactionOutput, matching Go SDK's outputJSON struct.
#[derive(Serialize, Deserialize)]
struct OutputJson {
    satoshis: u64,
    #[serde(rename = "lockingScript")]
    locking_script: String,
}

/// Intermediate JSON structure for deserialization that accepts optional fields.
/// This allows parsing JSON with only a `hex` field (no inputs/outputs).
#[derive(Deserialize)]
struct TxJsonOptional {
    #[serde(default)]
    hex: Option<String>,
    #[serde(default)]
    inputs: Option<Vec<InputJson>>,
    #[serde(default)]
    outputs: Option<Vec<OutputJson>>,
    #[serde(default)]
    version: Option<u32>,
    #[serde(rename = "lockTime", default)]
    lock_time: Option<u32>,
}

impl Transaction {
    /// Serializes this transaction to a JSON string.
    ///
    /// The JSON format matches the Go SDK's MarshalJSON output, including:
    /// - `txid`: The transaction ID (hex, reversed hash)
    /// - `hex`: The full serialized transaction as hex
    /// - `inputs`: Array of input objects with `unlockingScript`, `txid`, `vout`, `sequence`
    /// - `outputs`: Array of output objects with `satoshis`, `lockingScript`
    /// - `version`: The transaction version number
    /// - `lockTime`: The lock time
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// use bsv_rs::transaction::Transaction;
    ///
    /// let tx = Transaction::from_hex("0100000001...")?;
    /// let json = tx.to_json()?;
    /// println!("{}", json);
    /// ```
    pub fn to_json(&self) -> Result<String> {
        let tx_json = self.to_tx_json();
        serde_json::to_string(&tx_json).map_err(|e| {
            crate::Error::TransactionError(format!("JSON serialization failed: {}", e))
        })
    }

    /// Serializes this transaction to a pretty-printed JSON string.
    ///
    /// Same as `to_json()` but with indented formatting for readability.
    pub fn to_json_pretty(&self) -> Result<String> {
        let tx_json = self.to_tx_json();
        serde_json::to_string_pretty(&tx_json).map_err(|e| {
            crate::Error::TransactionError(format!("JSON serialization failed: {}", e))
        })
    }

    /// Deserializes a transaction from a JSON string.
    ///
    /// If the JSON contains a `hex` field, the transaction is parsed from the hex
    /// representation (matching Go SDK behavior). Otherwise, the transaction is
    /// reconstructed from the individual fields.
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// use bsv_rs::transaction::Transaction;
    ///
    /// let json = r#"{"hex": "0100000001...", "version": 1, "lockTime": 0, ...}"#;
    /// let tx = Transaction::from_json(json)?;
    /// ```
    pub fn from_json(json: &str) -> Result<Transaction> {
        let parsed: TxJsonOptional = serde_json::from_str(json).map_err(|e| {
            crate::Error::TransactionError(format!("JSON deserialization failed: {}", e))
        })?;

        // If hex is present, reconstruct from hex (matching Go SDK behavior)
        if let Some(ref hex) = parsed.hex {
            if !hex.is_empty() {
                return Transaction::from_hex(hex);
            }
        }

        // Otherwise, build from individual fields
        let version = parsed.version.unwrap_or(1);
        let lock_time = parsed.lock_time.unwrap_or(0);

        let inputs = match parsed.inputs {
            Some(input_jsons) => {
                let mut inputs = Vec::with_capacity(input_jsons.len());
                for ij in input_jsons {
                    inputs.push(input_from_json(&ij)?);
                }
                inputs
            }
            None => Vec::new(),
        };

        let outputs = match parsed.outputs {
            Some(output_jsons) => {
                let mut outputs = Vec::with_capacity(output_jsons.len());
                for oj in output_jsons {
                    outputs.push(output_from_json(&oj)?);
                }
                outputs
            }
            None => Vec::new(),
        };

        Ok(Transaction::with_params(
            version, inputs, outputs, lock_time,
        ))
    }

    /// Builds the internal TxJson representation for serialization.
    fn to_tx_json(&self) -> TxJson {
        let inputs: Vec<InputJson> = self
            .inputs
            .iter()
            .map(|input| {
                let txid = input.get_source_txid().unwrap_or_else(|_| "0".repeat(64));
                let unlocking_script = input
                    .unlocking_script
                    .as_ref()
                    .map(|s| s.to_hex())
                    .unwrap_or_default();
                InputJson {
                    unlocking_script,
                    txid,
                    vout: input.source_output_index,
                    sequence: input.sequence,
                }
            })
            .collect();

        let outputs: Vec<OutputJson> = self
            .outputs
            .iter()
            .map(|output| OutputJson {
                satoshis: output.satoshis.unwrap_or(0),
                locking_script: output.locking_script.to_hex(),
            })
            .collect();

        TxJson {
            txid: self.id(),
            hex: self.to_hex(),
            inputs,
            outputs,
            version: self.version,
            lock_time: self.lock_time,
        }
    }
}

/// Converts a JSON input representation to a TransactionInput.
fn input_from_json(ij: &InputJson) -> Result<TransactionInput> {
    let unlocking_script = if ij.unlocking_script.is_empty() {
        None
    } else {
        Some(UnlockingScript::from_hex(&ij.unlocking_script)?)
    };

    Ok(TransactionInput {
        source_transaction: None,
        source_txid: Some(ij.txid.clone()),
        source_output_index: ij.vout,
        unlocking_script,
        unlocking_script_template: None,
        sequence: ij.sequence,
    })
}

/// Converts a JSON output representation to a TransactionOutput.
fn output_from_json(oj: &OutputJson) -> Result<TransactionOutput> {
    let locking_script = if oj.locking_script.is_empty() {
        LockingScript::new()
    } else {
        LockingScript::from_hex(&oj.locking_script)?
    };

    Ok(TransactionOutput {
        satoshis: Some(oj.satoshis),
        locking_script,
        change: false,
    })
}

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

    // Known transaction hex from Go SDK test
    const GO_SDK_TX_HEX: &str = "0100000001abad53d72f342dd3f338e5e3346b492440f8ea821f8b8800e318f461cc5ea5a2010000006a4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8ffffffff02000000000000000008006a0548656c6c6f7f030000000000001976a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac00000000";

    // Expected JSON from Go SDK test (TestTx_MarshallJSON)
    const GO_SDK_EXPECTED_JSON: &str = r#"{"txid":"aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d","hex":"0100000001abad53d72f342dd3f338e5e3346b492440f8ea821f8b8800e318f461cc5ea5a2010000006a4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8ffffffff02000000000000000008006a0548656c6c6f7f030000000000001976a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac00000000","inputs":[{"unlockingScript":"4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8","txid":"a2a55ecc61f418e300888b1f82eaf84024496b34e3e538f3d32d342fd753adab","vout":1,"sequence":4294967295}],"outputs":[{"satoshis":0,"lockingScript":"006a0548656c6c6f"},{"satoshis":895,"lockingScript":"76a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac"}],"version":1,"lockTime":0}"#;

    #[test]
    fn test_tx_json_roundtrip() {
        // Parse from hex, serialize to JSON, deserialize, verify hex matches
        let tx = Transaction::from_hex(GO_SDK_TX_HEX).unwrap();
        let json = tx.to_json().unwrap();
        let tx2 = Transaction::from_json(&json).unwrap();

        assert_eq!(tx.to_hex(), tx2.to_hex());
        assert_eq!(tx.id(), tx2.id());
        assert_eq!(tx.version, tx2.version);
        assert_eq!(tx.lock_time, tx2.lock_time);
        assert_eq!(tx.inputs.len(), tx2.inputs.len());
        assert_eq!(tx.outputs.len(), tx2.outputs.len());
    }

    #[test]
    fn test_tx_json_serialize_known_tx() {
        // Serialize a known transaction and verify JSON fields match Go SDK
        let tx = Transaction::from_hex(GO_SDK_TX_HEX).unwrap();
        let json = tx.to_json().unwrap();

        assert_eq!(json, GO_SDK_EXPECTED_JSON);
    }

    #[test]
    fn test_tx_json_deserialize_known_json() {
        // Deserialize from Go SDK expected JSON and verify fields
        let tx = Transaction::from_json(GO_SDK_EXPECTED_JSON).unwrap();

        assert_eq!(tx.version, 1);
        assert_eq!(tx.lock_time, 0);
        assert_eq!(tx.inputs.len(), 1);
        assert_eq!(tx.outputs.len(), 2);
        assert_eq!(
            tx.id(),
            "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d"
        );
        assert_eq!(tx.to_hex(), GO_SDK_TX_HEX);
    }

    #[test]
    fn test_tx_json_empty_transaction() {
        // Empty transaction should serialize and deserialize
        let tx = Transaction::new();
        let json = tx.to_json().unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();

        assert_eq!(parsed["version"], 1);
        assert_eq!(parsed["lockTime"], 0);
        assert!(parsed["inputs"].as_array().unwrap().is_empty());
        assert!(parsed["outputs"].as_array().unwrap().is_empty());
        assert!(parsed["txid"].as_str().unwrap().len() == 64);
        assert!(!parsed["hex"].as_str().unwrap().is_empty());
    }

    #[test]
    fn test_tx_json_multiple_inputs_outputs() {
        // Create a transaction with multiple inputs and outputs
        let mut tx = Transaction::new();

        // Add inputs directly (bypassing validation for test)
        tx.inputs.push(TransactionInput {
            source_transaction: None,
            source_txid: Some(
                "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5".to_string(),
            ),
            source_output_index: 0,
            unlocking_script: Some(UnlockingScript::from_hex("00").unwrap()),
            unlocking_script_template: None,
            sequence: 0xFFFFFFFF,
        });

        tx.inputs.push(TransactionInput {
            source_transaction: None,
            source_txid: Some(
                "a2a55ecc61f418e300888b1f82eaf84024496b34e3e538f3d32d342fd753adab".to_string(),
            ),
            source_output_index: 2,
            unlocking_script: Some(UnlockingScript::from_hex("5151").unwrap()),
            unlocking_script_template: None,
            sequence: 0xFFFFFFFE,
        });

        tx.outputs.push(TransactionOutput::new(
            50000,
            LockingScript::from_hex("76a914000000000000000000000000000000000000000088ac").unwrap(),
        ));

        tx.outputs.push(TransactionOutput::new(
            0,
            LockingScript::from_hex("006a0548656c6c6f").unwrap(),
        ));

        let json = tx.to_json().unwrap();
        let tx2 = Transaction::from_json(&json).unwrap();

        assert_eq!(tx.to_hex(), tx2.to_hex());
        assert_eq!(tx.inputs.len(), 2);
        assert_eq!(tx.outputs.len(), 2);

        // Verify JSON structure
        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
        let inputs = parsed["inputs"].as_array().unwrap();
        assert_eq!(inputs.len(), 2);
        assert_eq!(inputs[0]["vout"], 0);
        assert_eq!(inputs[1]["vout"], 2);
        assert_eq!(inputs[1]["sequence"], 0xFFFFFFFEu32);

        let outputs = parsed["outputs"].as_array().unwrap();
        assert_eq!(outputs.len(), 2);
        assert_eq!(outputs[0]["satoshis"], 50000);
        assert_eq!(outputs[1]["satoshis"], 0);
    }

    #[test]
    fn test_tx_json_from_hex_only() {
        // Go SDK supports deserializing from JSON with only a hex field
        let json = format!(r#"{{"hex":"{}"}}"#, GO_SDK_TX_HEX);
        let tx = Transaction::from_json(&json).unwrap();

        assert_eq!(tx.to_hex(), GO_SDK_TX_HEX);
        assert_eq!(
            tx.id(),
            "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d"
        );
    }

    #[test]
    fn test_tx_json_from_fields_no_hex() {
        // Deserialize from JSON without a hex field (use individual fields)
        let json = r#"{
            "version": 1,
            "lockTime": 0,
            "inputs": [
                {
                    "unlockingScript": "00",
                    "txid": "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5",
                    "vout": 0,
                    "sequence": 4294967295
                }
            ],
            "outputs": [
                {
                    "satoshis": 1000,
                    "lockingScript": "76a914000000000000000000000000000000000000000088ac"
                }
            ]
        }"#;

        let tx = Transaction::from_json(json).unwrap();

        assert_eq!(tx.version, 1);
        assert_eq!(tx.lock_time, 0);
        assert_eq!(tx.inputs.len(), 1);
        assert_eq!(tx.outputs.len(), 1);
        assert_eq!(
            tx.inputs[0].source_txid.as_deref().unwrap(),
            "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5"
        );
        assert_eq!(tx.inputs[0].source_output_index, 0);
        assert_eq!(tx.inputs[0].sequence, 0xFFFFFFFF);
        assert_eq!(tx.outputs[0].satoshis, Some(1000));
    }

    #[test]
    fn test_tx_json_pretty_format() {
        let tx = Transaction::from_hex(GO_SDK_TX_HEX).unwrap();
        let pretty = tx.to_json_pretty().unwrap();

        // Pretty format should contain newlines and indentation
        assert!(pretty.contains('\n'));
        assert!(pretty.contains("  "));

        // Should still round-trip correctly
        let tx2 = Transaction::from_json(&pretty).unwrap();
        assert_eq!(tx.to_hex(), tx2.to_hex());
    }

    #[test]
    fn test_tx_json_field_names_match_go_sdk() {
        // Verify exact field names match Go SDK
        let tx = Transaction::from_hex(GO_SDK_TX_HEX).unwrap();
        let json = tx.to_json().unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();

        // Top-level fields
        assert!(parsed.get("txid").is_some(), "missing 'txid' field");
        assert!(parsed.get("hex").is_some(), "missing 'hex' field");
        assert!(parsed.get("inputs").is_some(), "missing 'inputs' field");
        assert!(parsed.get("outputs").is_some(), "missing 'outputs' field");
        assert!(parsed.get("version").is_some(), "missing 'version' field");
        assert!(parsed.get("lockTime").is_some(), "missing 'lockTime' field");

        // Input fields
        let input = &parsed["inputs"][0];
        assert!(
            input.get("unlockingScript").is_some(),
            "missing input 'unlockingScript' field"
        );
        assert!(input.get("txid").is_some(), "missing input 'txid' field");
        assert!(input.get("vout").is_some(), "missing input 'vout' field");
        assert!(
            input.get("sequence").is_some(),
            "missing input 'sequence' field"
        );

        // Output fields
        let output = &parsed["outputs"][0];
        assert!(
            output.get("satoshis").is_some(),
            "missing output 'satoshis' field"
        );
        assert!(
            output.get("lockingScript").is_some(),
            "missing output 'lockingScript' field"
        );
    }

    #[test]
    fn test_tx_json_invalid_json() {
        let result = Transaction::from_json("not valid json");
        assert!(result.is_err());
    }

    #[test]
    fn test_tx_json_invalid_hex() {
        let json = r#"{"hex":"not_valid_hex"}"#;
        let result = Transaction::from_json(json);
        assert!(result.is_err());
    }

    #[test]
    fn test_tx_json_go_sdk_multi_input_vector() {
        // This is the multi-input test case from Go SDK's TestTx_MarshallJSON
        let multi_input_hex = "0100000003d5da6f960610cc65153521fd16dbe96b499143ac8d03222c13a9b97ce2dd8e3c000000006b48304502210081214df575da1e9378f1d5a29dfd6811e93466a7222fb010b7c50dd2d44d7f2e0220399bb396336d2e294049e7db009926b1b30018ac834ee0cbca20b9d99f488038412102798913bc057b344de675dac34faafe3dc2f312c758cd9068209f810877306d66ffffffffd5da6f960610cc65153521fd16dbe96b499143ac8d03222c13a9b97ce2dd8e3c0200000069463043021f7059426d6aeb7d74275e52819a309b2bf903bd18b2b4d942d0e8e037681df702203f851f8a45aabfefdca5822f457609600f5d12a173adc09c6e7e2d4fdff7620a412102798913bc057b344de675dac34faafe3dc2f312c758cd9068209f810877306d66ffffffffd5da6f960610cc65153521fd16dbe96b499143ac8d03222c13a9b97ce2dd8e3c720000006b483045022100e7b3837f2818fe00a05293e0f90e9005d59b0c5c8890f22bd31c36190a9b55e9022027de4b77b78139ea21b9fd30876a447bbf29662bd19d7914028c607bccd772e4412102798913bc057b344de675dac34faafe3dc2f312c758cd9068209f810877306d66ffffffff01e8030000000000001976a914eb0bd5edba389198e73f8efabddfc61666969ff788ac00000000";

        let tx = Transaction::from_hex(multi_input_hex).unwrap();
        let json = tx.to_json().unwrap();
        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();

        // Verify the transaction structure matches Go SDK expectations
        assert_eq!(
            parsed["txid"].as_str().unwrap(),
            "41741af6fb64839c69f2385987eb3770c55c42eb6f7900fa2af9d667c42ceb20"
        );
        assert_eq!(parsed["version"], 1);
        assert_eq!(parsed["lockTime"], 0);

        let inputs = parsed["inputs"].as_array().unwrap();
        assert_eq!(inputs.len(), 3);

        // Verify each input's source txid and vout
        assert_eq!(
            inputs[0]["txid"].as_str().unwrap(),
            "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5"
        );
        assert_eq!(inputs[0]["vout"], 0);
        assert_eq!(inputs[0]["sequence"], 4294967295u64);

        assert_eq!(
            inputs[1]["txid"].as_str().unwrap(),
            "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5"
        );
        assert_eq!(inputs[1]["vout"], 2);

        assert_eq!(
            inputs[2]["txid"].as_str().unwrap(),
            "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5"
        );
        assert_eq!(inputs[2]["vout"], 114);

        let outputs = parsed["outputs"].as_array().unwrap();
        assert_eq!(outputs.len(), 1);
        assert_eq!(outputs[0]["satoshis"], 1000);

        // Round-trip verification
        let tx2 = Transaction::from_json(&json).unwrap();
        assert_eq!(tx.to_hex(), tx2.to_hex());
    }
}
