use std::{
    path::PathBuf,
    io,
};

use log::info;
use failure::Fail;

use reqwest::header::{ACCEPT, USER_AGENT, CONTENT_TYPE, COOKIE};

use crate::{
    imfs::{Imfs, FsError},
    project::{Project, ProjectLoadError},
    rbx_session::construct_oneoff_tree,
    rbx_snapshot::SnapshotError,
};

#[derive(Debug, Fail)]
pub enum UploadError {
    #[fail(display = "Roblox API Error: {}", _0)]
    RobloxApiError(String),

    #[fail(display = "Invalid asset kind: {}", _0)]
    InvalidKind(String),

    #[fail(display = "Project load error: {}", _0)]
    ProjectLoadError(#[fail(cause)] ProjectLoadError),

    #[fail(display = "IO error: {}", _0)]
    IoError(#[fail(cause)] io::Error),

    #[fail(display = "HTTP error: {}", _0)]
    HttpError(#[fail(cause)] reqwest::Error),

    #[fail(display = "XML model file error")]
    XmlModelEncodeError(rbx_xml::EncodeError),

    #[fail(display = "{}", _0)]
    FsError(#[fail(cause)] FsError),

    #[fail(display = "{}", _0)]
    SnapshotError(#[fail(cause)] SnapshotError),
}

impl_from!(UploadError {
    ProjectLoadError => ProjectLoadError,
    io::Error => IoError,
    reqwest::Error => HttpError,
    rbx_xml::EncodeError => XmlModelEncodeError,
    FsError => FsError,
    SnapshotError => SnapshotError,
});

#[derive(Debug)]
pub struct UploadOptions<'a> {
    pub fuzzy_project_path: PathBuf,
    pub security_cookie: String,
    pub asset_id: u64,
    pub kind: Option<&'a str>,
}

pub fn upload(options: &UploadOptions) -> Result<(), UploadError> {
    // TODO: Switch to uploading binary format?

    info!("Looking for project at {}", options.fuzzy_project_path.display());

    let project = Project::load_fuzzy(&options.fuzzy_project_path)?;

    info!("Found project at {}", project.file_location.display());
    info!("Using project {:#?}", project);

    let mut imfs = Imfs::new();
    imfs.add_roots_from_project(&project)?;
    let tree = construct_oneoff_tree(&project, &imfs)?;

    let root_id = tree.get_root_id();
    let mut contents = Vec::new();

    match options.kind {
        Some("place") | None => {
            let top_level_ids = tree.get_instance(root_id).unwrap().get_children_ids();
            rbx_xml::to_writer_default(&mut contents, &tree, top_level_ids)?;
        },
        Some("model") => {
            rbx_xml::to_writer_default(&mut contents, &tree, &[root_id])?;
        },
        Some(invalid) => return Err(UploadError::InvalidKind(invalid.to_owned())),
    }

    let url = format!("https://data.roblox.com/Data/Upload.ashx?assetid={}", options.asset_id);

    let client = reqwest::Client::new();
    let mut response = client.post(&url)
        .header(COOKIE, format!(".ROBLOSECURITY={}", &options.security_cookie))
        .header(USER_AGENT, "Roblox/WinInet")
        .header("Requester", "Client")
        .header(CONTENT_TYPE, "application/xml")
        .header(ACCEPT, "application/json")
        .body(contents)
        .send()?;

    if !response.status().is_success() {
        return Err(UploadError::RobloxApiError(response.text()?));
    }

    Ok(())
}