use config::Config;
use db;
use hyper::header::UserAgent;
use hyper::method::Method;
use nickel::{Continue, Halt, MediaType, Middleware, MiddlewareResult, Request, Response};
use nickel::status::StatusCode;
use plugin::Extensible;
use r2d2::{Config as PoolConfig, Pool};
use r2d2_postgres::{PostgresConnectionManager as Manager, TlsMode};
use std::error::Error;
use std::io::Write;
use std::str;
use std::sync::Arc;
use super::extensions::{IpAddrExtension, PostgresExtension};
use typemap::Key;
use uuid::Uuid;

pub struct ConfigMiddleware {
    config: Arc<Config>,
}

impl ConfigMiddleware {
    pub fn new(config: Arc<Config>) -> ConfigMiddleware {
        ConfigMiddleware { config: config }
    }
}

impl Key for ConfigMiddleware {
    type Value = Arc<Config>;
}

impl<D> Middleware<D> for ConfigMiddleware {
    fn invoke<'mw, 'conn>(&self, req: &mut Request<'mw, 'conn, D>, rep: Response<'mw, D>) -> MiddlewareResult<'mw, D> {
        req.extensions_mut().insert::<ConfigMiddleware>(self.config.clone());
        Ok(Continue(rep))
    }
}

pub struct PostgresMiddleware {
    pub pool: Pool<Manager>,
}

impl PostgresMiddleware {
    pub fn new(db_url: &str) -> Result<PostgresMiddleware, Box<Error>> {
        let manager = Manager::new(db_url, TlsMode::None)?;
        let pool = Pool::new(PoolConfig::default(), manager)?;
        Ok(PostgresMiddleware { pool: pool })
    }
}

impl Key for PostgresMiddleware {
    type Value = Pool<Manager>;
}

impl<D> Middleware<D> for PostgresMiddleware {
    fn invoke<'mw, 'conn>(&self, req: &mut Request<'mw, 'conn, D>, res: Response<'mw, D>) -> MiddlewareResult<'mw, D> {
        req.extensions_mut().insert::<PostgresMiddleware>(self.pool.clone());
        Ok(Continue(res))
    }
}

pub struct AuthMiddleware;

impl<D> Middleware<D> for AuthMiddleware {
    fn invoke<'mw, 'conn>(&self,
                          req: &mut Request<'mw, 'conn, D>,
                          mut res: Response<'mw, D>)
                          -> MiddlewareResult<'mw, D> {

        let (is_create_token, is_exists_token, is_api) = req.path_without_query().map(|p| (
            p == "/api/tokens",
            p.starts_with("/api/tokens/"),
            p.starts_with("/api"))
        ).unwrap_or((false, false, false));

        if (is_create_token && req.origin.method == Method::Post) || (is_exists_token && req.origin.method == Method::Head) || !is_api {
            Ok(Continue(res))
        } else if match req.origin.headers.get_raw("x-auth-token") {
            Some(header) if header.len() > 0 => {
                let value = try_with!(res, str::from_utf8(&header[0]).map_err(|err| (StatusCode::BadRequest, err)));
                let value = try_with!(res, value.parse::<Uuid>().map_err(|err| (StatusCode::BadRequest, err)));
                let conn = try_with!(res, req.pg_conn());
                let ip = req.ip_addr();
                let user_agent = req.origin.headers.get::<UserAgent>();
                try_with!(res, db::tokens::exists(
                    &*conn,
                    &value,
                    &format!("{}", ip),
                    user_agent.map(|h| &h.0).unwrap_or(&String::new())
                ).map_err(|err| (StatusCode::InternalServerError, err)))
        } {
            Ok(Continue(res))
        } else {
            res.set(StatusCode::Forbidden);
            res.set(MediaType::Json);
            let mut stream = res.start()?;
            if let Err(err) = stream.write_all(r#"{"data": "Access denied", "success": false}"#.as_bytes()) {
                stream.bail(format!("[AuthMiddleware] Unable to halt request: {}", err))
            } else {
                Ok(Halt(stream))
            }
        }
    }
}
            }
            _ => false,