use serde::Serialize;

use crate::{utils::{Headers, Values},
    view::{ViewBag, ViewData},
    ws::Writer
};

pub const HTTP_CONTINUE:               u16 = 100;
pub const HTTP_SWITCHING_PROTOCOLS:    u16 = 101;
pub const HTTP_OK:                     u16 = 200;
pub const HTTP_CREATED:                u16 = 201;
pub const HTTP_ACCEPTED:               u16 = 202;
pub const HTTP_NO_CONTENT:             u16 = 204;
pub const HTTP_MOVED_PERMANENTLY:      u16 = 301;
pub const HTTP_FOUND:                  u16 = 302;
pub const HTTP_SEE_OTHER:              u16 = 303;
pub const HTTP_NOT_MODIFIED:           u16 = 304;
pub const HTTP_TEMPORARY_REDIRECT:     u16 = 307;
pub const HTTP_PERMANENT_REDIRECT:     u16 = 308;
pub const HTTP_BAD_REQUEST:            u16 = 400;
pub const HTTP_UNAUTHORIZED:           u16 = 401;
pub const HTTP_FORBIDDEN:              u16 = 403;
pub const HTTP_NOT_FOUND:              u16 = 404;
pub const HTTP_METHOD_NOT_ALLOWED:     u16 = 405;
pub const HTTP_CONFLICT:               u16 = 409;
pub const HTTP_GONE:                   u16 = 410;
pub const HTTP_TOO_MANY_REQUESTS:      u16 = 429;
pub const HTTP_INTERNAL_SERVER_ERROR:  u16 = 500;
pub const HTTP_NOT_IMPLEMENTED:        u16 = 501;
pub const HTTP_BAD_GATEWAY:            u16 = 502;
pub const HTTP_SERVICE_UNAVAILABLE:    u16 = 503;
pub const HTTP_GATEWAY_TIMEOUT:        u16 = 504;

pub struct Response {
    pub ws: Option<Box<dyn Writer + Send + Sync + 'static>>,
    pub(crate) status_code: u16,
    pub(crate) headers: Headers,
    pub(crate) referer: String,
    pub(crate) body: Vec<u8>,
    pub(crate) view: Option<ViewBag>,
    pub(crate) errors: Values,
    pub(crate) old: Values,
    pub(crate) flashes: Values,
}

impl Response {
    pub fn new() -> Self {
        return Self {
            ws: None,
            status_code: HTTP_OK,
            headers: Headers::new(),
            referer: String::new(),
            body: vec![],
            view: None,
            errors: Values::new(),
            old: Values::new(),
            flashes: Values::new(),
        };
    }

    pub fn status_code(&mut self, code: u16) -> &mut Response {
        self.status_code = code;
        
        return self;
    }

    pub fn header(&mut self, key: &str, value: &str) -> &mut Response {
        self.headers.insert(key.to_owned(), value.to_owned());

        return self;
    }

    pub fn headers(&mut self, headers: Headers) -> &mut Response {
        self.headers.extend(headers);

        return self;
    }

    pub fn body(&mut self, body: &[u8]) -> &mut Response {
        self.body = body.to_vec();

        return self;
    }

    pub fn json<J>(&mut self, object: &J) -> &mut Response
    where 
        J: ?Sized + Serialize
    {
        return self.header("Content-Type", "application/json")
            .body(serde_json::to_string(object).unwrap().as_bytes());
    }

    pub fn html(&mut self, html: &str) -> &mut Response {
        return self.header("Content-Type", "text/html")
            .body(html.as_bytes());
    }

    pub fn view(&mut self, view: &str, data: Option<ViewData>) -> &mut Response {
        self.view = Some(ViewBag {
            view: String::from(view),
            data: data
        });

        return self;
    }

    pub fn redirect(&mut self, path: &str) -> &mut Response {
        return self.html(&self.redirect_document(path)).status_code(HTTP_TEMPORARY_REDIRECT);
    }

    pub fn redirect_permanent(&mut self, path: &str) -> &mut Response {
        return self.html(&self.redirect_document(path)).status_code(HTTP_PERMANENT_REDIRECT);
    }

    fn redirect_document(&self, to: &str) -> String {
        return format!(r#"
        <!DOCTYPE html>
        <meta http-equiv="Refresh" content="0, url='{}'">
        <head>
            <body>
            </body>
        </html>
        "#, to);
    }

    pub fn back(&mut self) -> &mut Self {
        if self.referer.is_empty() {
            return self.redirect("/");
        }
        
        return self.redirect(&self.referer.clone());
    }

    pub fn with_error(&mut self, name: &str, error: &str) -> &mut Response {
        self.errors.insert(name.to_string(), error.to_string());

        return self;
    }

    pub fn with_errors(&mut self, errors: Values) -> &mut Response {
        for (name, error) in errors {
            self.with_error(name.as_str(), error.as_str());
        }

        return self;
    }

    pub(crate) fn with_old(&mut self, old: Values) -> &mut Response {
        for (k, v) in old {
            self.old.insert(k, v);
        }

        return self;
    }

    pub fn with_flash(&mut self, key: &str, value: &str) -> &mut Response {
        self.flashes.insert(String::from(key), String::from(value));

        return self;
    }
}