use std::collections::HashMap;

use bytes::Bytes;
use log::{error, warn};
use serde::{Deserialize, Serialize};
use sqlx::Row;
use url::Url;

use crate::scrambler::Scrambler;

//TODO: should the table name include key fingerprint?
//TODO: what to do with state ids exceeding safe json integer range?
//TODO: should we allow customizable table prefix?

#[derive(Clone)]
pub struct DatabaseConnection {
	pool: sqlx::pool::Pool<sqlx::Any>,
}

impl DatabaseConnection {
	pub async fn new(url: Url) -> Result<DatabaseConnection, sqlx::Error> {
		let pool = sqlx::pool::PoolOptions::<sqlx::Any>::new().max_connections(4).min_connections(4).connect(url.as_str()).await?;
		let mut connection = pool.acquire().await?;
		if matches!(sqlx::query("SELECT true FROM ramistat_states_v1;").execute(&mut *connection).await, Err(sqlx::Error::Database(_))) {
			match connection.backend_name() {
				"PostgreSQL" => {
					sqlx::query("CREATE TABLE ramistat_states_v1 (id BIGSERIAL PRIMARY KEY, state JSONB UNIQUE NOT NULL);").execute(&mut *connection).await?
				}
				"SQLite" => {
					sqlx::query("CREATE TABLE ramistat_states_v1 (id INTEGER PRIMARY KEY AUTOINCREMENT, state JSONB UNIQUE NOT NULL);")
						.execute(&mut *connection)
						.await?
				}
				db => panic!("Database '{}' is not supported. Use postgresql in production, or optionally sqlite in development/testing.", db),
			};
		};
		Ok(DatabaseConnection { pool })
	}

	pub async fn load_state(&self, key_scrambler: &Scrambler, state_key: String) -> Result<serde_json::Value, (http::status::StatusCode, serde_json::Value)> {
		//std::thread::sleep(std::time::Duration::from_millis(2000));
		let Ok(state_id_u64) = key_scrambler.state_key_to_id(&state_key) else {
			warn!("Invalid state key submitted: {:?}", state_key);
			return Err((
				http::status::StatusCode::BAD_REQUEST,
				serde_json::json!({ "error": format!("State key is not in a valid format. State key: {:?}", state_key) }),
			));
		};
		let Ok(state_id) = i64::try_from(state_id_u64) else {
			warn!("Too-large state key submitted: {:?}", state_key);
			//TODO: does revealing this specific error reason reduce encryption strength (enough for us to care)?
			return Err((
				http::status::StatusCode::UNPROCESSABLE_ENTITY,
				serde_json::json!({ "error": format!("State key id is too large. State key: {:?}", state_key) }),
			));
		};
		match sqlx::query("SELECT state FROM ramistat_states_v1 WHERE id = $1::jsonb").bind(state_id).fetch_one(&self.pool).await {
			Ok(row) => Ok(serde_json::json!({ "state": serde_json::from_str::<serde_json::Value>(&row.get::<String,_>(0)).unwrap()})), //TODO: probably don't deserialize+serialize needlessly, and trust db returned a valid json
			Err(sqlx::Error::RowNotFound) => {
				warn!("State not found for key: {}", state_key);
				Err((http::status::StatusCode::NOT_FOUND, serde_json::json!({ "error": "state key not found" })))
			}
			Err(error) => {
				error!("Error loading state: {}", error);
				Err((http::status::StatusCode::INTERNAL_SERVER_ERROR, serde_json::json!({ "error": format!("Couldn't load state: {}", error) })))
			}
		}
	}

	pub async fn save_state(&self, key_scrambler: &Scrambler, body: Bytes) -> Result<serde_json::Value, (http::status::StatusCode, serde_json::Value)> {
		//std::thread::sleep(std::time::Duration::from_millis(2000));
		#[derive(Deserialize)]
		struct StatePostParams {
			state: NavigationStateObject,
		}

		#[derive(Serialize, Deserialize)]
		#[serde(transparent)]
		struct NavigationStateObject(HashMap<String, NavigationStateValue>);

		#[derive(Serialize, Deserialize)]
		#[serde(untagged)]
		enum NavigationStateValue {
			String(String),
			Object(NavigationStateObject),
		}

		let params: StatePostParams =
			serde_json::from_slice(&body).map_err(|error| (http::status::StatusCode::BAD_REQUEST, serde_json::json!({ "error": format!("{}",error) })))?;
		let state = serde_json::to_string(&params.state).unwrap();


		//NOTE: in 80% of cases, the state is already there in db, so we're optimistically fetching it at the cost of potential extra roundtrip to db
		//TODO: do the SELECT/INSERT in a single statement
		let state_id = match sqlx::query("SELECT id FROM ramistat_states_v1 WHERE state = $1::jsonb").bind(&state).fetch_one(&self.pool).await {
			Ok(row) => row.get::<i64, _>(0),
			Err(sqlx::Error::RowNotFound) => {
				match sqlx::query("INSERT INTO ramistat_states_v1(state) VALUES ($1::jsonb) ON CONFLICT DO NOTHING RETURNING id")
					.bind(&state)
					.fetch_one(&self.pool)
					.await
				{
					Ok(row) => row.get::<i64, _>(0),
					Err(error) => {
						error!("Unexpected database communication error while inserting state: {}", error);
						return Err((
							http::status::StatusCode::INTERNAL_SERVER_ERROR,
							serde_json::json!({ "error": format!("Unexpected database communication error while inserting state: {}", error) }),
						));
					}
				}
			}
			Err(error) => {
				error!("Unexpected database communication error while selecting state: {}", error);
				return Err((
					http::status::StatusCode::INTERNAL_SERVER_ERROR,
					serde_json::json!({ "error": format!("Unexpected database communication error while selecting state: {}", error) }),
				));
			}
		};
		//TODO check if id is not too big
		let key = key_scrambler.id_to_state_key(state_id as u64);
		Ok(serde_json::json!({ "key": key }))
	}
}
