1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//! This module represents all logic to work with indexer canister

use candid::Principal;
use ic_cdk::{query, update};
use crate::storable::Issuer;
use crate::access::is_controller;
use icrc_ledger_types::icrc1::account::Account;
use crate::state::{
    get_issuer,
    _set_issuer,
    get_issuers_batch
};
use crate::types::Icrc7TokenMetadata;


/// Adds an unverified issuer to the system.
///
/// # Arguments
/// * `principal` - The principal ID of the issuer to be added.
///
/// # Returns
/// * `Result<Issuer, String>` - Returns the newly created `Issuer` if successful, or an error message if failed.
///
/// # Errors
/// * Returns an error if the caller is not a controller.
/// * Returns an error if the issuer already exists.
#[update(name = "addUnverifiedIssuer")]
pub async fn add_unverified_issuer(principal: Principal) -> Result<Issuer, String> {
    let is_issuer = get_issuer(principal);

    if(!is_controller()) {
        return Err(String::from("Access denied"));
    }

    match is_issuer {
        Ok(_) => return Err(String::from("Issuer already exist")),
        Err(_) => {

            let minter: (Principal, ) = ic_cdk::call(principal, "getMinter", (&[Account {
                owner: principal,
                subaccount: None
            }],)).await.unwrap();
            
            let issuer = Issuer {
                reputation_module: minter.0.to_string(),
                issuer_type: String::from("null"),
                verified: false,
                name: String::from("null"),
                description: String::from("null")
            };

            _set_issuer(principal, issuer.clone())?;

            return Ok(issuer)
        },
    };
   
}

/// Verifies an existing issuer and updates their information.
///
/// # Arguments
/// * `principal` - The principal ID of the issuer to be verified.
/// * `issuer_type` - The type of the issuer.
/// * `metadata` - A tuple containing (reputation_module, name, description) of the issuer.
///
/// # Returns
/// * `Result<(), String>` - Returns `Ok(())` if successful, or an error message if failed.
///
/// # Errors
/// * Returns an error if the caller is not a controller.
/// * Returns an error if the issuer is not found.
#[update(name = "verifyIssuer")]
pub fn verify_issuer(principal: Principal, issuer_type: String, metadata: (String, String, String)) -> Result<(), String> {
    let issuer = get_issuer(principal);

    match issuer {
        Ok(_) => {
            if(!is_controller()) {
                return Err(String::from("Access denied"));
            }

            _set_issuer(principal, Issuer {
                issuer_type: issuer_type,
                verified: true,
                reputation_module: metadata.0,
                name: metadata.1,
                description: metadata.2
            })?;

            return Ok(())
        },
        Err(_) => {
            return Err(String::from("Issuer not found"))
        },
    };
   
}

/// Retrieves the achievements (token IDs) for a given principal across all issuers.
///
/// # Arguments
/// * `principal` - The principal ID to fetch achievements for.
///
/// # Returns
/// * `Result<Vec<(Principal, Vec<u128>)>, String>` - Returns a vector of tuples containing the issuer's principal and a vector of token IDs owned by the given principal.
///
/// # Errors
/// * Returns an error if there's an issue communicating with any of the issuers.
#[update(name = "getPrincipalAchievements")] 
pub async fn get_principal_achievements(principal: Principal) -> Result<Vec<(Principal, Vec<u128>)>, String> {
    let issuers = get_issuers_batch(None, 10);
    let mut collections_to_tokens: Vec<(Principal, Vec<u128>)> = vec![];

    for (key, value) in issuers {
        let balance: Result<(Vec<u128>, ) , _>= ic_cdk::call(key, "icrc7_balance_of", (&[Account {
            owner: principal,
            subaccount: None
        }],)).await;

        let mut balance_result: Vec<u128>;

        match balance {
            Ok(ok) => {
                balance_result = ok.0
            }
            Err(_) => {
                continue;
            }
        }

        if(balance_result.get(0).unwrap() == &0_u128) {continue};

        let owned: (Vec<u128>, ) = ic_cdk::call(key, "icrc7_tokens_of", (Account {
            owner: principal,
            subaccount: None
        }, None::<u128>, balance_result.get(0).unwrap() - 1,)).await.unwrap();

        collections_to_tokens.push((key, owned.0))
    }
    
    Ok(collections_to_tokens)
}

/// Retrieves the achievements with metadata for a given principal across all issuers.
///
/// # Arguments
/// * `principal` - The principal ID to fetch achievements for.
///
/// # Returns
/// * `Result<Vec<(Principal, Vec<Icrc7TokenMetadata>)>, String>` - Returns a vector of tuples containing the issuer's principal and a vector of token metadata owned by the given principal.
///
/// # Errors
/// * Returns an error if there's an issue communicating with any of the issuers or fetching metadata.
#[update(name = "getPrincipalAchievementsMetadata")] 
pub async fn get_principal_achievements_with_metadata(principal: Principal) -> Result<Vec<(Principal, Vec<Icrc7TokenMetadata>)>, String> {
    let tokens = get_principal_achievements(principal).await.unwrap();
    let mut collections_to_tokens_with_metadata: Vec<(Principal, Vec<Icrc7TokenMetadata>)> = vec![];

    for (key, value) in tokens {
        let metadata: (Vec<Option<Icrc7TokenMetadata>>, ) = ic_cdk::call(key, "icrc7_token_metadata", (value,)).await.unwrap();

        let metadata_some: Vec<Icrc7TokenMetadata> = metadata.0.into_iter().filter_map(|x| x).collect();

        if(metadata_some.len() > 0) {
            collections_to_tokens_with_metadata.push((key, metadata_some))
        }
    }
    
    Ok(collections_to_tokens_with_metadata)
}