use std::ops::ControlFlow;

use bestool_postgres::error::format_miette_error;
use tokio::{fs::File, io};
use tracing::{error, warn};

use super::{
	state::ReplContext, transaction::TransactionState, write_mode::mark_write_mode_active,
};
use crate::{parser::QueryModifier, query::execute_query};

pub async fn handle_execute(
	ctx: &mut ReplContext<'_>,
	_input: String,
	sql: String,
	modifiers: crate::parser::QueryModifiers,
) -> ControlFlow<()> {
	let output_file_path = modifiers.iter().find_map(|m| {
		if let QueryModifier::Output { file_path } = m {
			Some(file_path.clone())
		} else {
			None
		}
	});

	let use_colours =
		if output_file_path.is_some() || ctx.repl_state.lock().unwrap().output_file.is_some() {
			false
		} else {
			ctx.config.use_colours
		};

	let result = if let Some(path) = output_file_path {
		if std::path::Path::new(&path).exists() {
			error!("File already exists: {path}");
			eprintln!("Error: File already exists: {}", path);
			return ControlFlow::Continue(());
		}

		match File::create(&path).await {
			Ok(mut file) => {
				let mut vars = {
					let state = ctx.repl_state.lock().unwrap();
					state.vars.clone()
				};
				let mut query_ctx = crate::query::QueryContext {
					config: ctx.config,
					client: ctx.client,
					pool: ctx.pool,
					modifiers: modifiers.clone(),
					writer: &mut file,
					use_colours,
					vars: Some(&mut vars),
					repl_state: ctx.repl_state,
					schema_cache_manager: Some(ctx.schema_cache_manager),
					redact_mode: ctx.redact_mode,
				};
				let result = execute_query(&sql, &mut query_ctx).await;
				ctx.repl_state.lock().unwrap().vars = vars;
				result
			}
			Err(e) => {
				error!("Failed to open output file '{path}': {e}");
				return ControlFlow::Continue(());
			}
		}
	} else {
		let file_arc_opt = ctx.repl_state.lock().unwrap().output_file.clone();
		if let Some(file_arc) = file_arc_opt {
			let mut vars = {
				let state = ctx.repl_state.lock().unwrap();
				state.vars.clone()
			};

			let mut file = file_arc.lock().await;
			let mut query_ctx = crate::query::QueryContext {
				config: ctx.config,
				client: ctx.client,
				pool: ctx.pool,
				modifiers: modifiers.clone(),
				writer: &mut *file,
				use_colours,
				vars: Some(&mut vars),
				repl_state: ctx.repl_state,
				schema_cache_manager: Some(ctx.schema_cache_manager),
				redact_mode: ctx.redact_mode,
			};
			let result = execute_query(&sql, &mut query_ctx).await;

			ctx.repl_state.lock().unwrap().vars = vars;
			result
		} else {
			let mut stdout = io::stdout();
			let mut vars = {
				let state = ctx.repl_state.lock().unwrap();
				state.vars.clone()
			};
			let mut query_ctx = crate::query::QueryContext {
				config: ctx.config,
				client: ctx.client,
				pool: ctx.pool,
				modifiers,
				writer: &mut stdout,
				use_colours,
				vars: Some(&mut vars),
				repl_state: ctx.repl_state,
				schema_cache_manager: Some(ctx.schema_cache_manager),
				redact_mode: ctx.redact_mode,
			};
			let result = execute_query(&sql, &mut query_ctx).await;
			ctx.repl_state.lock().unwrap().vars = vars;
			result
		}
	};

	match result {
		Ok(()) => {
			mark_write_mode_active(ctx.repl_state);
			let tx_state = TransactionState::check(ctx.monitor_client, ctx.backend_pid).await;
			if ctx.repl_state.lock().unwrap().write_mode
				&& matches!(tx_state, TransactionState::None)
				&& let Err(e) = ctx.client.batch_execute("BEGIN").await
			{
				warn!("Failed to start transaction: {e}");
			}
		}
		Err(e) => {
			eprintln!("{}", format_miette_error(&e, None));
		}
	}

	ControlFlow::Continue(())
}
