use anyhow::{Error, Result};
use minreq::Response;

pub trait Doctor {
    fn test(&self, a_url: impl Into<String>) -> Result<String>;
}

#[derive(Debug)]
pub struct FakeDoctor {
    test_return: bool,
}

impl FakeDoctor {
    pub fn new(test_return: bool) -> Self {
        Self { test_return }
    }
}

impl Doctor for FakeDoctor {
    fn test(&self, _a_url: impl Into<String>) -> Result<String> {
        match self.test_return {
            true => Ok("All good!".into()),
            false => Err(Error::msg("An error..")),
        }
    }
}

trait StatusCode {
    fn success(&self) -> bool;
}

impl StatusCode for Response {
    fn success(&self) -> bool {
        self.status_code.ge(&200) && self.status_code.le(&299)
    }
}

#[derive(Debug)]
pub struct DefaultDoctor {
    timeout: u64,
}

impl DefaultDoctor {
    pub fn new(timeout: u64) -> Self {
        Self { timeout }
    }
}

impl Doctor for DefaultDoctor {
    fn test(&self, a_url: impl Into<String>) -> Result<String> {
        let response = minreq::get(a_url.into())
            .with_timeout(self.timeout)
            .send()?;
        match response.success() {
            true => Ok("Status code between 200 and 299".into()),
            false => Err(Error::msg(format!("Status code: {}", response.status_code))),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::doctor::{Doctor, FakeDoctor};

    #[test]
    fn test_fake_success() {
        assert!(FakeDoctor::new(true).test("").is_ok())
    }

    #[test]
    fn test_fake_fail() {
        assert!(FakeDoctor::new(false).test("").is_err())
    }
}
