use std::fs;
use std::path::{Path, PathBuf};

use url::Url;

use crate::error::{EbookmError, Result};
use crate::manifest::SourceDefinition;

#[derive(Debug, Clone)]
pub enum SourceSpec {
    SubstackUrl(Url),
    LocalHtml(PathBuf),
}

#[derive(Debug, Clone)]
pub enum SourceOrigin {
    Remote(Url),
    LocalFile(PathBuf),
}

#[derive(Debug, Clone)]
pub struct LoadedSource {
    pub origin: SourceOrigin,
    pub html: String,
}

impl SourceSpec {
    pub fn from_definition(definition: &SourceDefinition, manifest_dir: &Path) -> Result<Self> {
        match definition {
            SourceDefinition::Substack { url } => Ok(SourceSpec::SubstackUrl(
                Url::parse(url).map_err(|source| EbookmError::UrlParse {
                    value: url.clone(),
                    source,
                })?,
            )),
            SourceDefinition::Html { path } => {
                let joined = manifest_dir.join(path);
                Ok(SourceSpec::LocalHtml(joined))
            }
        }
    }
}

pub fn load_source(spec: &SourceSpec) -> Result<LoadedSource> {
    match spec {
        SourceSpec::SubstackUrl(url) => {
            let client = reqwest::blocking::Client::builder()
                .user_agent("ebookm/0.1")
                .build()
                .map_err(|source| EbookmError::Request {
                    url: url.to_string(),
                    source,
                })?;
            let html = client
                .get(url.clone())
                .send()
                .and_then(|response| response.error_for_status())
                .map_err(|source| EbookmError::Request {
                    url: url.to_string(),
                    source,
                })?
                .text()
                .map_err(|source| EbookmError::Request {
                    url: url.to_string(),
                    source,
                })?;
            Ok(LoadedSource {
                origin: SourceOrigin::Remote(url.clone()),
                html,
            })
        }
        SourceSpec::LocalHtml(path) => {
            let html = fs::read_to_string(path).map_err(|source| EbookmError::Io {
                path: path.display().to_string(),
                source,
            })?;
            Ok(LoadedSource {
                origin: SourceOrigin::LocalFile(path.clone()),
                html,
            })
        }
    }
}

pub fn resolve_relative_url(origin: &SourceOrigin, href: &str) -> Option<Url> {
    match origin {
        SourceOrigin::Remote(base) => base.join(href).ok(),
        SourceOrigin::LocalFile(path) => {
            if let Ok(url) = Url::parse(href) {
                return Some(url);
            }
            let parent = path.parent()?;
            let joined = parent.join(href);
            Url::from_file_path(joined).ok()
        }
    }
}
