use std::fmt;
use std::io::{ErrorKind, Error};
use serde::{Deserialize, Serialize};
use reqwest::{get, Error as ReqwestError};

pub enum MajorDimension {
    Rows,
    Columns
}

impl fmt::Display for MajorDimension {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let as_str = match self {
            MajorDimension::Rows => "ROWS",
            MajorDimension::Columns => "COLUMNS",
        };
        write!(f, "{}", as_str)
    }
}

pub enum DateTimeRenderOption {
    FormattedString,
    SerialNumber,
}

impl fmt::Display for DateTimeRenderOption {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let as_str = match self {
            DateTimeRenderOption::FormattedString => "FORMATTED_STRING",
            DateTimeRenderOption::SerialNumber => "SERIAL_NUMBER",
        };
        write!(f, "{}", as_str)
    }
}

pub trait Buildable<Target, B: Builder<Target>> {
    fn builder() -> B;
}

pub trait Builder<T> {
    fn new() -> Self;
    async fn build(self) -> Result<T, ReqwestError>;
}

impl Buildable<GoogleSheet, SheetBuilder> for GoogleSheet {
    fn builder() -> SheetBuilder {
        SheetBuilder::new()
    }
}

pub struct SheetBuilder {
    google_sheet: GoogleSheet,
}

impl SheetBuilder {
    pub fn with_sheet_id(mut self, sheet_id: &str) -> Self {
        self.google_sheet.sheet_id = sheet_id.to_string();
        self
    }

    pub fn with_key(mut self, key: &str) -> Self {
        self.google_sheet.key = key.to_string();
        self
    }

    pub fn with_major_dimension(mut self, major_dimension: MajorDimension) -> Self {
        self.google_sheet.major_dimension = major_dimension;
        self
    }

    pub fn with_date_time_render_option(mut self, date_time_render_option: DateTimeRenderOption) -> Self {
        self.google_sheet.date_time_render_option = date_time_render_option;
        self
    }
}

impl Builder<GoogleSheet> for SheetBuilder {
    fn new() -> Self {
        Self {
            google_sheet: GoogleSheet {
                sheet_id: String::default(),
                key: String::default(),
                major_dimension: MajorDimension::Rows,
                date_time_render_option: DateTimeRenderOption::FormattedString,
                spreadsheet: None,
            }
        }
    }

    async fn build(self) -> Result<GoogleSheet, ReqwestError> {
        let mut url = String::from(SHEET_URL);
        url.push_str(&self.google_sheet.sheet_id);
        url.push_str("?key=");
        url.push_str(&self.google_sheet.key);

        let sheet = get(&url).await?.json::<BaseSpreadsheet>().await?;
        Ok(GoogleSheet {
            spreadsheet: Some(sheet),
            ..self.google_sheet
        })
    }
}

const SHEET_URL: &str = "https://sheets.googleapis.com/v4/spreadsheets/";

pub struct GoogleSheet {
    key: String,
    sheet_id: String,
    major_dimension: MajorDimension,
    date_time_render_option: DateTimeRenderOption,
    spreadsheet: Option<BaseSpreadsheet>,
}

impl GoogleSheet {
    async fn fetch(&self, sheet_name: &str) -> Result<SpreadsheetTab, ReqwestError> {
        let mut url = String::from(SHEET_URL);
        url.push_str(&self.spreadsheet.as_ref().unwrap().spreadsheet_id);
        url.push_str("/values/");
        url.push_str(sheet_name);
        url.push_str("?majorDimension=");
        url.push_str(&self.major_dimension.to_string());
        url.push_str("&dateTimeRenderOption=");
        url.push_str(&self.date_time_render_option.to_string());
        url.push_str("&key=");
        url.push_str(&self.key);
        let sheet = get(url).await?.json::<SpreadsheetTab>().await?;
        Ok(sheet)
    }

    pub async fn fetch_sheet(&self, sheet_name: &str) -> Result<SpreadsheetTab, Error> {
        let found_sheet = self.spreadsheet.as_ref().unwrap().sheets.iter().find(|sheet: &&Sheet| {
            sheet.properties.title == sheet_name
        });

        if found_sheet.is_none() {
            return Err(Error::new(ErrorKind::NotFound, format!("SheetNotFoundError: {}", sheet_name)));
        }

        let sheet = self.fetch(sheet_name).await;
        match sheet {
            Ok(sheet) => Ok(sheet),
            Err(error) => Err(Error::new(ErrorKind::InvalidData, error.to_string()))
        }
    }
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Spreadsheet {
    range: String,
    major_dimension: String,
    values: Vec<Vec<String>>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct SpreadsheetTab {
    pub range: String,
    #[serde(rename = "majorDimension")]
    pub major_dimension: String,
    pub values: Vec<Vec<String>>,
}


#[derive(Serialize, Deserialize, Debug)]
struct GridProperties {
    #[serde(rename = "rowCount")]
    pub row_count: i64,
    #[serde(rename = "columnCount")]
    pub column_count: i64,
    #[serde(rename = "frozenRowCount")]
    pub frozen_row_count: Option<i64>,
}

#[derive(Serialize, Deserialize, Debug)]
struct Properties {
    #[serde(rename = "sheetId")]
    pub sheet_id: i64,
    pub title: String,
    pub index: i64,
    #[serde(rename = "sheetType")]
    pub sheet_type: String,
    #[serde(rename = "gridProperties")]
    pub grid_properties: GridProperties,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Sheet {
    properties: Properties,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct BaseSpreadsheet {
    spreadsheet_id: String,
    spreadsheet_url: String,
    sheets: Vec<Sheet>,
}


// TODO FIX THESE TESTS
// #[cfg(test)]
// mod tests {
//     use super::*;
//     use httpmock::prelude::*;
//     use tokio;
//
//
//     #[tokio::test]
//     async fn test_fetch_sheet_success() {
//         let server = MockServer::start_async().await;
//
//         // let mock_url = "https://sheets.googleapis.com/v4/spreadsheets/Sheet1/?key=test_key".to_string();
//
//         let hello_mock = server.mock_async(|when, then| {
//             when.method("GET")
//                 .path("sheets.googleapis.com/v4/spreadsheets/Sheet1/?key=test_key")
//                 .query_param("key", "test_key");
//             then.status(200)
//                 .header("content-type", "text/html; charset=UTF-8")
//                 .body("BALLS");
//         }).await;
//
//         // let initial_sheet: Mock = server
//         //     .mock("GET", "https://sheets.googleapis.com/v4/spreadsheets/Sheet1/?key=test_key")
//         //     // .with_status(200)
//         //     //.with_header("content-type", "application/json")
//         //     .with_body("BALLS")
//         //     .with_body(r#"
//         //         {
//         //             "spreadsheetId": "Sheet1",
//         //             "properties": {
//         //             "title": "My Test Sheet",
//         //             "locale": "en_US",
//         //             "autoRecalc": "ON_CHANGE",
//         //             "timeZone": "America/Mexico_City"
//         //         },
//         //         "sheets": [{
//         //             "properties": {
//         //                 "sheetId": 0,
//         //                 "title": "Tab1",
//         //                 "index": 0,
//         //                 "sheetType": "GRID",
//         //                 "gridProperties": {
//         //                     "rowCount": 7540,
//         //                     "columnCount": 18,
//         //                     "frozenRowCount": 2
//         //                 }
//         //             }
//         //         },
//         //         {
//         //             "properties": {
//         //                 "sheetId": 956919132,
//         //                 "title": "Tab2",
//         //                 "index": 1,
//         //                 "sheetType": "GRID",
//         //                 "gridProperties": {
//         //                     "rowCount": 987,
//         //                     "columnCount": 18,
//         //                     "frozenRowCount": 2
//         //                 }
//         //             }
//         //         }
//         //     ],
//         //     "spreadsheetUrl": "https://docs.google.com/spreadsheets/d/Sheet1/edit"
//         // }"#)
//        //  .create_async().await;
//
//         // let _m1: Mock = server
//         //     .mock("GET", "https://sheets.googleapis.com/v4/spreadsheets/Sheet1/values/Tab1?majorDimension=ROWS&dateTimeRenderOption=FORMATTED_STRING&key=tesk_key")
//         //     .with_status(200)
//         //     .with_header("content-type", "application/json")
//         //     .with_body(r#"
//         //         {
//         //             "range": "Tab1!A1:R10",
//         //             "majorDimension": "ROWS",
//         //             "values": [
//         //                 [
//         //                     "Bob",
//         //                     "261",
//         //                     "10/21/2020",
//         //                     "9/28/2022",
//         //                     "707",
//         //                     "$49,343.00",
//         //                     "$189.05",
//         //                     "$31,808.26",
//         //                     "181.40%",
//         //                     "n"
//         //                 ],
//         //                 [
//         //                     "",
//         //                     "Steve",
//         //                     "224",
//         //                     "10/14/2020",
//         //                     "6/21/2024",
//         //                     "1346",
//         //                     "$36,772.00",
//         //                     "$164.16",
//         //                     "$21,849.13",
//         //                     "146.41%",
//         //                     "n"
//         //                 ],
//         //                 [
//         //                     "Clint",
//         //                     "158",
//         //                     "11/18/2020",
//         //                     "6/19/2024",
//         //                     "1309",
//         //                     "$34,247.00",
//         //                     "$216.75",
//         //                     "$25,273.06",
//         //                     "281.63%",
//         //                     "n"
//         //                 ]
//         //             ]
//         //         }"#).create();
//
//
//         let google_sheet = GoogleSheet::new("", "").build().await;
//         hello_mock.assert_async().await;
//         assert!(google_sheet.is_ok());
//         let google_sheet = google_sheet.unwrap();
//
//         let result = google_sheet.fetch_sheet("Tab1").await;
//
//         assert!(result.is_ok());
//         let sheet = result.unwrap();
//         assert_eq!(sheet.range, "Sheet1!A1:B2");
//         assert_eq!(sheet.major_dimension, "ROWS");
//         assert_eq!(sheet.values[0], vec!["Name", "Age"]);
//         assert_eq!(sheet.values[1], vec!["John", "30"]);
//     }
//     #[tokio::test]
//     async fn test_fetch_sheet_not_found() {
//         let base_spreadsheet = BaseSpreadsheet {
//             spreadsheet_id: "sheet_id".to_string(),
//             spreadsheet_url: "https://sheets.googleapis.com/v4/spreadsheets/sheet_id".to_string(),
//             sheets: vec![], // Empty sheets list
//         };
//
//         let google_sheet = GoogleSheet {
//             key: "test_key".to_string(),
//             spreadsheet: base_spreadsheet,
//         };
//
//         let result = google_sheet.fetch_sheet("Sheet1").await;
//
//         // Assert that the result is an error with NotFound kind
//         assert!(result.is_err());
//         let error = result.unwrap_err();
//         assert_eq!(error.kind(), std::io::ErrorKind::NotFound);
//         assert_eq!(error.to_string(), "SheetNotFoundError: Sheet1");
//     }
// }
