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
/*!
 * The module for download of IB Flex Query reports.
 *
 * <https://guides.interactivebrokers.com/reportingreference/reportguide/activity%20flex%20query%20reference.htm>
 */

use chrono::Local;

use crate::{config::get_dl_config, flex_statement};

const FLEX_URL: &str = "https://gdcdyn.interactivebrokers.com/Universal/servlet/";
const REQUEST_ENDPOINT: &str = "FlexStatementService.SendRequest";
const STMT_ENDPOINT: &str = "FlexStatementService.GetStatement";

/**
 * Parameters for the download.
 */
#[derive(Debug, Default)]
pub struct DownloadParams {
    pub query_id: Option<u32>,
    pub token: Option<String>,
}

impl DownloadParams {
    pub fn new(query_id: Option<u32>, token: &Option<String>) -> Self {
        Self {
            query_id,
            token: match token {
                Some(tkn) => Some(tkn.to_owned()),
                None => None,
            },
        }
    }
}

/**
 * Downloads the Flex Query Cash Transactions report into a file in the current directory.
 */
pub async fn download(params: DownloadParams) -> String {
    // get the configuration
    let cfg = get_dl_config(params);

    // download the report
    let report = download_report(&cfg.flex_query_id, &cfg.ib_token).await;

    // save to text file
    let today_date = Local::now().date_naive();
    let today = today_date.format("%Y-%m-%d");
    let output_filename = format!("{today}_cash-tx.xml");

    std::fs::write(&output_filename, report).expect("successfully saved");

    output_filename
}

/**
 * FlexQueryReport is downloaded in a 2-step process.
 * You need to supply the token (Reports / Settings / FlexWeb Service),
 * and the query id (Reports / Flex Queries / Custom Flex Queries / Configure).
 */
async fn download_report(query_id: &str, token: &str) -> String {
    let resp = request_statement(query_id, token).await;
    // parse
    let stmt_resp = flex_statement::parse_response_text(&resp);

    // Now request the actual report.
    let stmt_text = download_statement_text(&stmt_resp.reference_code, token).await;

    stmt_text
}

/**
 * Requests the statement. Receives the request id.
 * Returns the text of the response, the content is xml.
 */
async fn request_statement(query_id: &str, token: &str) -> String {
    let url = format!("{FLEX_URL}{REQUEST_ENDPOINT}?v=3&t={token}&q={query_id}");

    let client = reqwest::Client::new();
    let res = client
        .get(url)
        .header("User-Agent", "Java")
        .send()
        .await
        .expect("response received");

    res.text().await.expect("contents of the response")
}

/**
 * Downloads the actual report. 2nd step.
 * Requires the reference code received in the 1st step.
 */
async fn download_statement_text(ref_code: &String, token: &str) -> String {
    let url = format!("{FLEX_URL}{STMT_ENDPOINT}?v=3&q={ref_code}&t={token}");

    reqwest::get(url)
        .await
        .expect("downloaded statement")
        .text()
        .await
        .expect("text response (xml)")
}

#[cfg(test)]
mod tests {
    use crate::{download::{FLEX_URL, REQUEST_ENDPOINT, request_statement, DownloadParams}};

    #[test]
    /// Test concatenating constants.
    fn constants_test() {
        let actual = format!("{FLEX_URL}{REQUEST_ENDPOINT}");

        assert_eq!("https://gdcdyn.interactivebrokers.com/Universal/servlet/FlexStatementService.SendRequest",
        actual);
    }

    /**
     * Test sending the Flex request. This is step 1 of the 2-step process.
     *
     * To run the test, create ibflex.toml config and populate with valid parameters.
     * Uncomment the [tokio::test] line below.
     */
    // #[tokio::test]
    #[allow(unused)]
    async fn request_report_test() {
        let cfg = crate::config::get_dl_config(DownloadParams::default());
        let actual = request_statement(&cfg.flex_query_id, &cfg.ib_token).await;
        
        println!("received: {:?}", actual);

        assert_ne!(String::default(), actual);
        assert!(!actual.contains("ERROR"));

        assert!(false);
    }

    // /**
    //  * Request the full Flex report, using 2-step process.
    //  *
    //  * To run the test, create ibflex.toml config and populate with valid parameters.
    //  */
    // #[tokio::test]
    // async fn report_download_test() {
    //     let cfg = crate::config::get_dl_config(DownloadParams::default());
    //     let result = download_report(&cfg.flex_query_id, &cfg.ib_token).await;
    //     assert!(result.contains("FlexQueryResponse"));
    // }
}