// Copyright 2026 Jordan Castro <jordan@grupojvm.com>
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
//
use crate::{
    borrow_string, create_raw_string, free_raw_string, pxs_debug, python::{consume_error, pocketpy, var::pocketpyref_to_var, var_to_pocketpyref}, shared::{func::call_function, pxs_Runtime, var::pxs_Var}
};

/// Use instead of the py_arg macro.
pub(super) unsafe fn py_get_arg(argv: pocketpy::py_StackRef, i: usize) -> pocketpy::py_StackRef {
    unsafe { argv.add(i) }
}

/// Use instead of the py_assign macro.
pub(super) unsafe fn py_assign(left: pocketpy::py_Ref, right: pocketpy::py_Ref) {
    if left.is_null() || right.is_null() {
        return;
    }

    unsafe {
        *left = *right;
    }
}

pub(super) unsafe fn get_from_obj(obj: pocketpy::py_Ref, key: &str) -> Option<pocketpy::py_Ref> {
    unsafe { 
        let c_key = create_raw_string!(key);
        let pyname = pocketpy::py_name(c_key);
        free_raw_string!(c_key);

        let ok = pocketpy::py_getattr(obj, pyname);
        if !ok {
            #[allow(unused)]
            let err = consume_error();
            pxs_debug!("{err}");
            None
        } else {
            Some(pocketpy::py_retval())
        }
    }
}

pub(super) unsafe fn get_string_from_obj(obj: pocketpy::py_Ref, key: String) -> String {
    unsafe {
        let c_key = create_raw_string!(key);
        let pyname = pocketpy::py_name(c_key);
        let res = pocketpy::py_getattr(obj, pyname);
        if res {
            let r0 = pocketpy::py_retval();
            let val = pocketpy::py_tostr(r0);
            let val = borrow_string!(val);
            free_raw_string!(c_key);
            val.to_string().clone()
        } else {
            let err = consume_error();
            pxs_debug!("Python error when trying to get a string from obj: {err}");
            err
        }
    }
}

pub(super) unsafe fn get_builtin(key: &str) -> pocketpy::py_Ref {
    unsafe{
        let ckey = create_raw_string!(key);
        let module_name = create_raw_string!("builtins");
        let builtins = pocketpy::py_getmodule(module_name);
        let res = pocketpy::py_getdict(builtins, pocketpy::py_name(ckey));
        free_raw_string!(ckey);
        free_raw_string!(module_name);
        res
    }
}

#[allow(unused)]
pub(super) unsafe fn get_global(key: &str) -> pocketpy::py_Ref {
    unsafe{
        let ckey = create_raw_string!(key);
        let res = pocketpy::py_getglobal(pocketpy::py_name(ckey));
        free_raw_string!(ckey);
        res
    }
}

// /// Use instead of py_setattr
// pub(super) unsafe fn py_setattr(_self: pocketpy::py_Ref, name: &str, val: pocketpy::py_Ref) {
//     let name = create_raw_string!(name);
//     unsafe {
//         let pyname = pocketpy::py_name(name);
//         pocketpy::py_setattr(_self, pyname, val);
//         free_raw_string!(name);
//     }
// }

pub(super) unsafe fn raise(msg: &str) -> bool {
    let c_msg = create_raw_string!(msg);
    unsafe {
        let ret_slot = pocketpy::py_retval();
        pocketpy::py_newstr(ret_slot, c_msg);
        free_raw_string!(c_msg);

        return true;
    }
}

/// The pocketpy bridge
pub(super) unsafe extern "C" fn pocketpy_bridge(argc: i32, argv: pocketpy::py_StackRef) -> bool {
    // Future Self: Why do I do argc < 1 ? Weird...
    // As for why... it's because we always require the function `idx` as the first argument. And if it is not
    // there then someone is hacking.
    if argc < 1 {
        unsafe {
            return raise("Python: argc < 1");
        }
    }
    // let c_name = unsafe { pocketpy::py_tostr(py_get_arg(argv, 0)) };
    // let name = borrow_string!(c_name);
    // let fn_idx = get_fn_idx_from_name(name);
    let fn_idx = unsafe { pocketpy::py_toint(py_get_arg(argv, 0)) };
    if fn_idx < 0 {
        unsafe {
            return raise("Python: fn_idx is < 0");
        }
    }

    // Convert argv into Vec<Var>
    let mut vars: Vec<pxs_Var> = vec![];

    // Add the runtime
    vars.push(pxs_Var::new_i64(pxs_Runtime::pxs_Python as i64));

    // Convert py_Ref into pxs_Var.
    for i in 1..argc {
        let arg_ref = unsafe { py_get_arg(argv, i as usize) };
        vars.push(pocketpyref_to_var(arg_ref));
    }

    // Call internal function
    let mut success = true;
    unsafe {
        let res = call_function(fn_idx as i32, vars);
        let tmp = pocketpy::py_pushtmp();
        // let ret_slot = pocketpy::py_retval();

        var_to_pocketpyref(tmp, &res, None);

        py_assign(pocketpy::py_retval(), tmp);
        pocketpy::py_pop();

        if res.is_exception() {
            success = false;
        }
    }

    success
}
