use std::{collections::HashMap, net::IpAddr};

use rocket::{
	form::{self, Contextual, Form},
	response::Redirect,
};
use rocket_dyn_templates::{context, Template};

use crate::{
	db::{
		doll,
		schema::{CreateDollProfile, DollProfile, DollTagsDb, User},
	},
	ids::{id_public_to_db, pick_ids},
	pages::{CommonTemplateState, FakeContext},
	routes::{account, error_handlers::PageResult, public},
};

#[get("/new_tag")]
pub async fn show_register(db: DollTagsDb, _user: User, meta: CommonTemplateState) -> PageResult {
	let ids = pick_ids(db).await?;

	Ok(Template::render(
		"register_tag",
		context! {
			mode: "register",
			ids,
			previous: form::Context::default(),
			meta,
		},
	)
	.into())
}

impl From<DollProfile> for FakeContext {
	fn from(tag: DollProfile) -> Self {
		FakeContext {
			values: HashMap::from([
				("id", vec![format!("{:0>6}", tag.id)]),
				(
					"microchip_id",
					vec![tag.microchip_id.unwrap_or(String::from(""))],
				),
				("name", vec![tag.name]),
				("pronoun_subject", vec![tag.pronoun_subject]),
				("pronoun_object", vec![tag.pronoun_object]),
				("pronoun_possessive", vec![tag.pronoun_possessive]),
				("handler_name", vec![tag.handler_name]),
				(
					"handler_link",
					vec![tag.handler_link.unwrap_or(String::from(""))],
				),
				("kind", vec![tag.kind.unwrap_or(String::from(""))]),
				("breed", vec![tag.breed.unwrap_or(String::from(""))]),
				("behaviour", vec![tag.behaviour.unwrap_or(String::from(""))]),
				(
					"description",
					vec![tag.description.unwrap_or(String::from(""))],
				),
				(
					"chassis_type",
					vec![tag.chassis_type.unwrap_or(String::from(""))],
				),
				(
					"chassis_id",
					vec![tag.chassis_id.unwrap_or(String::from(""))],
				),
				(
					"chassis_color",
					vec![tag.chassis_color.unwrap_or(String::from(""))],
				),
			]),
			errors: HashMap::new(),
			form_errors: vec![],
		}
	}
}

#[get("/edit_tag/<id>")]
pub async fn show_edit_tag(
	mut db: DollTagsDb,
	id: &str,
	_user: User,
	meta: CommonTemplateState,
) -> PageResult {
	let normalized_id = match id_public_to_db(id) {
		Some(v) => v,
		None => return Ok(Redirect::to(uri!("/account", account::index)).into()),
	};
	let tag = match doll::get(&mut *db, normalized_id, "", true).await? {
		Some(v) => v,
		None => return Ok(Redirect::to(uri!("/account", account::index)).into()),
	};

	Ok(Template::render(
		"register_tag",
		context! {
			mode: "edit",
			id,
			previous: FakeContext::from(tag),
			meta,
		},
	)
	.into())
}

#[derive(Debug, FromForm)]
pub struct TagForm<'a> {
	#[field(validate=validate_id())]
	pub ident: &'a str,
	#[field(validate=len(..32))]
	pub microchip_id: &'a str,

	#[field(validate=len(1..=256))]
	pub name: &'a str,
	#[field(validate=len(1..=32))]
	pub pronoun_subject: &'a str,
	#[field(validate=len(1..=32))]
	pub pronoun_object: &'a str,
	#[field(validate=len(1..=32))]
	pub pronoun_possessive: &'a str,

	#[field(validate=len(1..=256))]
	pub handler_name: &'a str,
	#[field(validate=len(..=2048))]
	pub handler_link: &'a str,

	#[field(validate=len(..=256))]
	pub kind: &'a str,
	#[field(validate=len(..=256))]
	pub breed: &'a str,
	#[field(validate=len(..=256))]
	pub behaviour: &'a str,
	#[field(validate=len(..=2048))]
	pub description: &'a str,

	#[field(validate=validate_chassis(self.chassis_id, self.chassis_color, "chassis_type"))]
	pub chassis_type: &'a str,
	#[field(validate=validate_chassis(self.chassis_type, self.chassis_color, "chassis_id"))]
	pub chassis_id: &'a str,
	#[field(validate=validate_chassis(self.chassis_type, self.chassis_id, "chassis_color"))]
	pub chassis_color: &'a str,
}

fn validate_id<'v>(id: &str) -> form::Result<'v, ()> {
	if let None = id_public_to_db(id) {
		Err(form::Error::validation("id not formatted properly"))?;
	}

	Ok(())
}

fn validate_chassis<'v>(a: &str, b: &str, c: &str, field: &str) -> form::Result<'v, ()> {
	let all_empty = a.len() == 0 && b.len() == 0 && c.len() == 0;
	let all_full = a.len() != 0
		&& a.len() <= 256
		&& b.len() != 0
		&& b.len() <= 256
		&& c.len() != 0
		&& c.len() <= 256;

	if !all_empty && !all_full {
		Err(form::Error::validation(format!("missing {}", field)))?;
	}

	Ok(())
}

#[post("/new_tag", data = "<tag>")]
pub async fn handle_register(
	mut db: DollTagsDb,
	tag: Form<Contextual<'_, TagForm<'_>>>,
	user: User,
	meta: CommonTemplateState,
	client_ip: IpAddr,
) -> PageResult {
	let tag = match tag.value {
		Some(ref values) => values,
		None => {
			// in case the form validation fails, this will be tasked with rendering the page again with submitted values and display errors
			let ids = pick_ids(db).await?;

			debug!("tag registration form invalid, context: {:?}", &tag.context);

			return Ok(Template::render(
				"register_tag",
				context! {
					mode: "register",
					ids,
					previous: &tag.context,
					meta,
				},
			)
			.into());
		}
	};

	fn normalize_opt(opt: &str) -> Option<&str> {
		if opt.len() != 0 {
			Some(opt)
		} else {
			None
		}
	}

	let id = id_public_to_db(&tag.ident).expect("id format was wrong but is now right??");
	let normalized_microchip_id = tag.microchip_id.to_lowercase();
	let microchip_id = normalize_opt(&normalized_microchip_id);

	if doll::id_exists(&mut *db, id, microchip_id.unwrap_or("")).await? {
		// TODO: that's weird... what was i expecting to do here?
		return Ok(Redirect::found(uri!("/account", show_register)).into());
	}

	let creation_status = doll::create(
		&mut *db,
		CreateDollProfile {
			id,
			microchip_id,
			name: tag.name,
			pronoun_subject: tag.pronoun_subject,
			pronoun_object: tag.pronoun_object,
			pronoun_possessive: tag.pronoun_possessive,
			handler_name: tag.handler_name,
			handler_link: normalize_opt(tag.handler_link),
			kind: normalize_opt(tag.kind),
			breed: normalize_opt(tag.breed),
			behaviour: normalize_opt(tag.behaviour),
			description: normalize_opt(tag.description),
			chassis_type: normalize_opt(tag.chassis_type),
			chassis_id: normalize_opt(tag.chassis_id),
			chassis_color: normalize_opt(tag.chassis_color),
			bound_to_id: &user.id,
		},
	)
	.await;

	if creation_status.is_err() {
		warn!(
			"[audit|{}] [{}] failed to register tag {}",
			client_ip,
			user.id.to_string(),
			tag.ident
		);
	}
	creation_status?;

	warn!(
		"[audit|{}] [{}] registered tag {}",
		client_ip,
		user.id.to_string(),
		tag.ident
	);

	Ok(Redirect::to(uri!(public::show_profile(Some(tag.ident), microchip_id))).into())
}

#[post("/edit_tag/<id>", data = "<tag>")]
pub async fn handle_edit_tag(
	mut db: DollTagsDb,
	id: &str,
	tag: Form<Contextual<'_, TagForm<'_>>>,
	user: User,
	meta: CommonTemplateState,
) -> PageResult {
	let id = match id_public_to_db(id) {
		None => return Ok(Redirect::to(uri!("/account", crate::routes::account::index)).into()),
		Some(v) => v,
	};
	let tag = match tag.value {
		Some(ref values) => values,
		None => {
			debug!("tag edition form invalid, context: {:?}", &tag.context);

			return Ok(Template::render(
				"register_tag",
				context! {
					mode: "edit",
					id,
					previous: &tag.context,
					meta,
				},
			)
			.into());
		}
	};

	fn normalize_opt(opt: &str) -> Option<&str> {
		if opt.len() != 0 {
			Some(opt)
		} else {
			None
		}
	}

	let normalized_microchip_id = tag.microchip_id.to_lowercase();
	let microchip_id = normalize_opt(&normalized_microchip_id);

	doll::edit(
		&mut *db,
		CreateDollProfile {
			id,
			microchip_id,
			name: tag.name,
			pronoun_subject: tag.pronoun_subject,
			pronoun_object: tag.pronoun_object,
			pronoun_possessive: tag.pronoun_possessive,
			handler_name: tag.handler_name,
			handler_link: normalize_opt(tag.handler_link),
			kind: normalize_opt(tag.kind),
			breed: normalize_opt(tag.breed),
			behaviour: normalize_opt(tag.behaviour),
			description: normalize_opt(tag.description),
			chassis_type: normalize_opt(tag.chassis_type),
			chassis_id: normalize_opt(tag.chassis_id),
			chassis_color: normalize_opt(tag.chassis_color),
			bound_to_id: &user.id,
		},
	)
	.await?;

	Ok(Redirect::to(uri!(public::show_profile(Some(tag.ident), microchip_id))).into())
}
