use ghidra::{
    Address, AddressRange, AnalysisMode, AnalysisOptions, AnalysisReport, BasicBlockInfo,
    CallGraphEdge, CallGraphNode, ControlFlowEdgeInfo, ControlFlowGraph, DataFlowEdge,
    DataFlowGraph, DataFlowOp, DataFlowVariable, DataFlowVarnode, DataInfo, DecompileOptions,
    DecompileResult, DecompileStatus, Decompiler, Error, ExportInfo, ExportKind,
    FunctionControlFlowGraph, FunctionDescriptor, FunctionPrototype, FunctionSummary, Ghidra,
    GhidraRuntime, HighPcodeBlock, HighPcodeGraph, HighSymbol, ImportInfo, ImportKind,
    InstructionInfo, LaunchOptions, LoadedProgram, LoaderArgument, MemoryBlockInfo, NamedCount,
    OperandInfo, PcodeOpInfo, PcodeSummary, Program, ProgramCallGraph, ProgramEnumValue,
    ProgramFunction, ProgramFunctionSignature, ProgramLoadInfo, ProgramLoadOptions,
    ProgramMetadata, ProgramOpenOptions, ProgramParameter, ProgramPath, ProgramSymbol,
    ProgramTransaction, ProgramType, ProgramTypeComponent, ProgramTypeDetails,
    ProgramTypeIndexError, Project, ReferenceInfo, Result, RuntimeError, SourceMapInfo, SymbolInfo,
    SymbolKind, SymbolSource, TaskMonitorOptions, TaskMonitorReport, VarnodeInfo, start, started,
};

#[test]
fn public_surface_exports_lifecycle_handles_and_models() {
    let address = Address {
        space: "ram".to_string(),
        offset: 0x1000,
        text: "ram:00001000".to_string(),
    };
    let range = AddressRange {
        start: address.clone(),
        end: address.clone(),
    };
    let reference = ReferenceInfo {
        from: address.clone(),
        to: address.clone(),
        reference_type: "UNCONDITIONAL_CALL".to_string(),
        operand_index: 0,
        primary: true,
        external: false,
        call: true,
        data: false,
        read: false,
        write: false,
    };
    let varnode = VarnodeInfo {
        space: "register".to_string(),
        offset: 0,
        size: 8,
        text: "(register, 0, 8)".to_string(),
        address: Some(address.clone()),
        constant: false,
        register: true,
        unique: false,
        address_tied: false,
        input: false,
        persistent: false,
    };
    let pcode_op = PcodeOpInfo {
        sequence_number: "ram:00001000:0".to_string(),
        sequence_target: address.clone(),
        sequence_time: 0,
        sequence_order: 0,
        mnemonic: "COPY".to_string(),
        opcode: 1,
        output: Some(varnode.clone()),
        inputs: vec![varnode.clone()],
    };
    let instruction = InstructionInfo {
        address: address.clone(),
        mnemonic: "CALL".to_string(),
        pcode: vec![pcode_op.clone()],
        operands: vec![OperandInfo {
            index: 0,
            text: "ram:00001000".to_string(),
            objects: vec!["ram:00001000".to_string()],
            references: vec![reference.clone()],
        }],
        length: 5,
        bytes: vec![0xe8, 0, 0, 0, 0],
        fallthrough: Some(address.clone()),
        flow_type: "CALL".to_string(),
    };
    let data = DataInfo {
        address: address.clone(),
        range: range.clone(),
        data_type: "string".to_string(),
        length: 4,
        value: Some("test".to_string()),
        display: "\"test\"".to_string(),
    };
    let symbol = SymbolInfo {
        name: "main".to_string(),
        address: address.clone(),
        kind: SymbolKind::Function,
        source: SymbolSource::Analysis,
        namespace: "Global".to_string(),
        primary: true,
        external: false,
    };
    let block = MemoryBlockInfo {
        name: ".text".to_string(),
        range: range.clone(),
        size: 16,
        initialized: true,
        read: true,
        write: false,
        execute: true,
        volatile: false,
        overlay: false,
    };
    let decompile = DecompileResult {
        status: DecompileStatus::Completed,
        error_message: None,
        c: Some("int main(void) { return 0; }".to_string()),
        signature: Some("int main(void)".to_string()),
        prototype: Some(FunctionPrototype {
            return_type: "int".to_string(),
            return_type_id: Some("type:int".to_string()),
            calling_convention: Some("__stdcall".to_string()),
            parameter_count: 0,
            varargs: false,
            inline: false,
            no_return: false,
            has_this_pointer: false,
        }),
        pcode: Some(PcodeSummary {
            op_count: 1,
            op_counts: vec![NamedCount {
                name: "RETURN".to_string(),
                count: 1,
            }],
            basic_block_count: 1,
            varnode_count: 1,
            varnode_space_counts: vec![NamedCount {
                name: "register".to_string(),
                count: 1,
            }],
        }),
        high_pcode: Some(HighPcodeGraph {
            blocks: vec![HighPcodeBlock {
                id: "high_block_0".to_string(),
                index: 0,
                block_type: "basic".to_string(),
                range: Some(range.clone()),
                op_sequence_numbers: vec![pcode_op.sequence_number.clone()],
            }],
            edges: Vec::new(),
            ops: vec![pcode_op.clone()],
        }),
        data_flow: Some(DataFlowGraph {
            ops: vec![DataFlowOp {
                id: "op_0".to_string(),
                sequence_number: pcode_op.sequence_number.clone(),
                mnemonic: pcode_op.mnemonic.clone(),
                opcode: pcode_op.opcode,
                block_id: Some("high_block_0".to_string()),
            }],
            varnodes: vec![DataFlowVarnode {
                id: "varnode_0".to_string(),
                kind: "register".to_string(),
                variable_id: Some("variable_0".to_string()),
                varnode: varnode.clone(),
            }],
            variables: vec![DataFlowVariable {
                id: "variable_0".to_string(),
                name: "argc".to_string(),
                data_type: "int".to_string(),
                type_id: Some("type:int".to_string()),
                size: 4,
                storage: "register".to_string(),
                kind: "parameter".to_string(),
                symbol_id: Some(1),
                symbol_address: Some(address.clone()),
                symbol_type: Some("PARAMETER".to_string()),
                namespace: Some("main".to_string()),
            }],
            edges: vec![
                DataFlowEdge {
                    source_id: "varnode_0".to_string(),
                    destination_id: "op_0".to_string(),
                    kind: "input".to_string(),
                    slot: Some(0),
                },
                DataFlowEdge {
                    source_id: "op_0".to_string(),
                    destination_id: "varnode_0".to_string(),
                    kind: "output".to_string(),
                    slot: None,
                },
            ],
        }),
        parameters: vec![HighSymbol {
            name: "argc".to_string(),
            data_type: "int".to_string(),
            type_id: Some("type:int".to_string()),
            size: 4,
            storage: "register".to_string(),
        }],
        local_symbols: vec![HighSymbol {
            name: "local_0".to_string(),
            data_type: "int".to_string(),
            type_id: Some("type:int".to_string()),
            size: 4,
            storage: "stack[-0x4]".to_string(),
        }],
    };
    let descriptor = FunctionDescriptor {
        entry: address.clone(),
        name: "main".to_string(),
    };
    let call_graph = ProgramCallGraph {
        nodes: vec![
            CallGraphNode {
                id: "function:ram:00001000".to_string(),
                kind: "function".to_string(),
                name: "main".to_string(),
                address: Some(address.clone()),
                namespace: Some("Global".to_string()),
                library: None,
                function_id: Some("function:ram:00001000".to_string()),
                symbol_id: Some("symbol:Global%1fmain%1ffunction".to_string()),
            },
            CallGraphNode {
                id: "external:libc:puts:".to_string(),
                kind: "external".to_string(),
                name: "puts".to_string(),
                address: None,
                namespace: None,
                library: Some("libc".to_string()),
                function_id: None,
                symbol_id: None,
            },
        ],
        edges: vec![CallGraphEdge {
            id: "call:function:ram:00001000:ram:00001000:0:external:libc:puts:".to_string(),
            source_id: "function:ram:00001000".to_string(),
            destination_id: "external:libc:puts:".to_string(),
            callsite: address.clone(),
            reference_type: "UNCONDITIONAL_CALL".to_string(),
            operand_index: 0,
            computed: false,
            thunk_resolved: false,
        }],
    };
    let type_ref = ProgramType {
        id: "type:int%1fint%1f4%1f/%1fbuiltin".to_string(),
        name: "int".to_string(),
        display_name: "int".to_string(),
        size: 4,
        alignment: 4,
        category_path: Some("/".to_string()),
        details: ProgramTypeDetails::Builtin,
    };
    let metadata = ProgramMetadata {
        symbols: vec![ProgramSymbol {
            id: "symbol:Global%1fmain%1ffunction".to_string(),
            name: "main".to_string(),
            kind: "function".to_string(),
            source: "analysis".to_string(),
            address: address.clone(),
            namespace: "Global".to_string(),
            primary: true,
            external: false,
            type_id: None,
        }],
        functions: vec![ProgramFunction {
            id: "function:ram:00001000".to_string(),
            name: "main".to_string(),
            entry: address.clone(),
            namespace: "Global".to_string(),
            external: false,
            thunk: false,
            thunked_function_id: None,
            symbol_id: Some("symbol:Global%1fmain%1ffunction".to_string()),
            signature: ProgramFunctionSignature {
                display: "int main(void)".to_string(),
                calling_convention: "__stdcall".to_string(),
                return_type_id: Some(type_ref.id.clone()),
                parameters: vec![ProgramParameter {
                    ordinal: 0,
                    name: "argc".to_string(),
                    type_id: Some(type_ref.id.clone()),
                    storage: "register".to_string(),
                }],
                varargs: false,
                no_return: false,
            },
        }],
        types: vec![type_ref.clone()],
    };
    let control_flow = ControlFlowGraph {
        blocks: vec![BasicBlockInfo {
            id: "block_0".to_string(),
            name: "main".to_string(),
            range: range.clone(),
            flow_type: "CALL".to_string(),
            instruction_addresses: vec![address.clone()],
        }],
        edges: vec![ControlFlowEdgeInfo {
            source_block_id: "block_0".to_string(),
            destination_block_id: "block_0".to_string(),
            flow_type: "FALL_THROUGH".to_string(),
            source_address: Some(address.clone()),
            destination_address: Some(address.clone()),
            reference_address: Some(address.clone()),
        }],
    };
    let import = ImportInfo {
        name: "puts".to_string(),
        library: Some("<EXTERNAL>".to_string()),
        kind: ImportKind::Function,
        address: Some(address.clone()),
        reference_types: vec!["UNCONDITIONAL_CALL".to_string()],
    };
    let export = ExportInfo {
        name: "main".to_string(),
        address: address.clone(),
        kind: ExportKind::EntryPoint,
    };
    let source_map = SourceMapInfo {
        file_path: "sample.c".to_string(),
        function_name: "main".to_string(),
        line_start: Some(1),
        line_end: Some(3),
    };
    let summary = FunctionSummary {
        function: descriptor.clone(),
        basic_block_count: 1,
        instruction_count: 1,
        callers: vec![address.clone()],
        callees: vec![address.clone()],
        strings: vec!["test".to_string()],
        constants: vec!["0x0".to_string()],
        data_refs: vec![address.clone()],
        imports: vec![import.clone()],
        exports: vec![export.clone()],
        source_map: Some(source_map.clone()),
    };
    let function_control_flow = FunctionControlFlowGraph {
        function: descriptor.clone(),
        graph: control_flow.clone(),
    };

    assert_eq!(address.as_str(), "ram:00001000");
    assert_eq!(instruction.operands[0].references, vec![reference]);
    assert_eq!(data.range, range);
    assert_eq!(symbol.kind, SymbolKind::Function);
    assert!(block.execute);
    assert_eq!(descriptor.name, "main");
    assert_eq!(control_flow.blocks[0].instruction_addresses[0], address);
    assert_eq!(import.kind, ImportKind::Function);
    assert_eq!(export.kind, ExportKind::EntryPoint);
    assert_eq!(source_map.function_name, "main");
    assert_eq!(summary.function, descriptor);
    assert_eq!(summary.imports[0].name, "puts");
    assert_eq!(function_control_flow.graph, control_flow);
    assert_eq!(decompile.status, DecompileStatus::Completed);
    assert_eq!(call_graph.edges[0].destination_id, call_graph.nodes[1].id);
    assert_eq!(metadata.functions[0].signature.parameters[0].name, "argc");
    assert_eq!(metadata.types[0].details, ProgramTypeDetails::Builtin);
    let type_index = metadata.type_index().expect("type index builds");
    assert_eq!(
        type_index
            .signature_return_type(&metadata.functions[0].signature)
            .expect("return type resolves")
            .expect("return type is present")
            .id,
        type_ref.id
    );
    let _index_error = ProgramTypeIndexError::MissingType {
        id: "type:missing".to_string(),
    };
    let _type_component = ProgramTypeComponent {
        ordinal: 0,
        name: "value".to_string(),
        offset: 0,
        length: 4,
        type_id: Some(type_ref.id.clone()),
        bit_size: None,
        bit_offset: None,
        comment: None,
    };
    let _enum_value = ProgramEnumValue {
        name: "One".to_string(),
        value: "1".to_string(),
        comment: None,
    };

    let _start: fn(LaunchOptions) -> Result<GhidraRuntime> = start;
    let _started: fn() -> bool = started;
    let _ghidra_start: fn(LaunchOptions) -> Result<Ghidra> = Ghidra::start;
    let _program_path = ProgramPath::root("sample").expect("valid path");
    let _open_options = ProgramOpenOptions::new(ProgramPath::root("sample").expect("valid path"));
    let _load_options = ProgramLoadOptions::new("sample")
        .expect("valid load options")
        .with_loader("ghidra.app.util.opinion.ElfLoader")
        .with_language("x86:LE:64:default")
        .with_compiler("gcc")
        .with_loader_arg("key", "value");
    let _loader_arg = LoaderArgument {
        name: "key".to_string(),
        value: "value".to_string(),
    };
    let _load_info = ProgramLoadInfo {
        opened_existing: false,
        project_path: "/sample".to_string(),
        folder: "/".to_string(),
        name: "sample".to_string(),
        loader: None,
        language: "x86:LE:64:default".to_string(),
        compiler: "gcc".to_string(),
        monitor: TaskMonitorReport {
            timeout_seconds: None,
            cancelled: false,
            message: String::new(),
            progress: 0,
            maximum: 0,
        },
    };
    let _analysis_options = AnalysisOptions {
        mode: AnalysisMode::IfNeeded,
        monitor: TaskMonitorOptions::none(),
    };
    let _analysis_report = AnalysisReport {
        mode: AnalysisMode::IfNeeded,
        analyzed: false,
        already_analyzed: true,
        message_log: String::new(),
        monitor: TaskMonitorReport {
            timeout_seconds: Some(5),
            cancelled: false,
            message: String::new(),
            progress: 0,
            maximum: 0,
        },
    };
    let _decompile_options = DecompileOptions::new(60)
        .with_monitor(TaskMonitorOptions::timeout(std::time::Duration::from_secs(5)).unwrap());
    let _result: Result<()> = Ok(());
    let _error: Option<Error> = None;
    let _runtime_error: Option<RuntimeError> = None;
    let _project: Option<Project> = None;
    let _program: Option<Program<'_>> = None;
    let _loaded_program: Option<LoadedProgram<'_>> = None;
    let _transaction: Option<ProgramTransaction<'_>> = None;
    let _decompiler: Option<Decompiler<'_>> = None;
}
