// {{{ Imports
use std::io::Cursor;

use anyhow::anyhow;
use image::{DynamicImage, ImageBuffer};
use poise::serenity_prelude::{CreateAttachment, CreateEmbed};
use poise::CreateReply;

use crate::arcaea::achievement::GoalStats;
use crate::arcaea::chart::Level;
use crate::arcaea::jacket::BITMAP_IMAGE_SIZE;
use crate::arcaea::play::{compute_b30_ptt, get_best_plays};
use crate::arcaea::rating::rating_as_float;
use crate::arcaea::score::ScoringSystem;
use crate::assets::{
	get_difficulty_background, with_font, B30_BACKGROUND, COUNT_BACKGROUND, EXO_FONT,
	GRADE_BACKGROUND, NAME_BACKGROUND, PTT_EMBLEM, SCORE_BACKGROUND, STATUS_BACKGROUND,
	TOP_BACKGROUND,
};
use crate::bitmap::{Align, BitmapCanvas, Color, LayoutDrawer, LayoutManager, Rect};
use crate::context::{Error, PoiseContext, TaggedError};
use crate::logs::debug_image_log;
use crate::user::User;

use super::discord::MessageContext;
// }}}

// {{{ Stats
/// Query various stats.
#[poise::command(
	prefix_command,
	slash_command,
	subcommands("meta", "b30", "bany"),
	subcommand_required
)]
pub async fn stats(_ctx: PoiseContext<'_>) -> Result<(), Error> {
	Ok(())
}
// }}}
// {{{ Render best plays
async fn best_plays<C: MessageContext>(
	ctx: &mut C,
	user: &User,
	scoring_system: ScoringSystem,
	grid_size: (u32, u32),
	require_full: bool,
) -> Result<(), TaggedError> {
	let user_ctx = ctx.data();
	let plays = get_best_plays(
		user_ctx,
		user.id,
		scoring_system,
		if require_full {
			grid_size.0 * grid_size.1
		} else {
			grid_size.0 * (grid_size.1.max(1) - 1) + 1
		} as usize,
		(grid_size.0 * grid_size.1) as usize,
		None,
	)?;

	// {{{ Layout
	let mut layout = LayoutManager::default();
	let jacket_area = layout.make_box(BITMAP_IMAGE_SIZE, BITMAP_IMAGE_SIZE);
	let jacket_with_border = layout.margin_uniform(jacket_area, 3);
	let jacket_margin = 10;
	let jacket_with_margin = layout.margin(
		jacket_with_border,
		jacket_margin,
		jacket_margin,
		2,
		jacket_margin,
	);
	let top_left_area = layout.make_box(90, layout.height(jacket_with_margin));
	let top_area = layout.glue_vertically(top_left_area, jacket_with_margin);
	let bottom_area = layout.make_box(layout.width(top_area), 43);
	let bottom_in_area = layout.margin_xy(bottom_area, -20, -7);
	let item_area = layout.glue_horizontally(top_area, bottom_area);
	let item_with_margin = layout.margin_xy(item_area, 22, 17);
	let (item_grid, item_origins) =
		layout.repeated_evenly(item_with_margin, (grid_size.0, grid_size.1));
	let root = layout.margin_uniform(item_grid, 30);
	// }}}
	// {{{ Rendering prep
	let width = layout.width(root);
	let height = layout.height(root);

	let canvas = BitmapCanvas::new(width, height);
	let mut drawer = LayoutDrawer::new(layout, canvas);
	// }}}
	// {{{ Render background
	let bg = &*B30_BACKGROUND;

	let scale = (drawer.layout.width(root) as f32 / bg.width() as f32)
		.max(drawer.layout.height(root) as f32 / bg.height() as f32)
		.max(1.0)
		.ceil() as u32;

	drawer.blit_rbg_scaled_up(
		root,
		// Align the center of the image with the center of the root
		Rect::from_image(bg).scaled(scale).align(
			(Align::Center, Align::Center),
			drawer.layout.lookup(root).center(),
		),
		bg.dimensions(),
		bg.as_raw(),
		scale,
	);
	// }}}

	for (i, origin) in item_origins.enumerate() {
		drawer
			.layout
			.edit_to_relative(item_with_margin, item_grid, origin.0, origin.1);

		let top_bg = &*TOP_BACKGROUND;
		drawer.blit_rbga(top_area, (0, 0), top_bg);

		let (play, song, chart) = if let Some(item) = plays.get(i) {
			item
		} else {
			break;
		};

		// {{{ Display index
		let bg = &*COUNT_BACKGROUND;
		let bg_center = Rect::from_image(bg).center();

		// Draw background
		drawer.blit_rbga(item_area, (-8, jacket_margin), bg);
		with_font(&EXO_FONT, |faces| {
			drawer.text(
				item_area,
				(bg_center.0 - 12, bg_center.1 - 3 + jacket_margin),
				faces,
				crate::bitmap::TextStyle {
					size: 25,
					weight: Some(800),
					color: Color::WHITE,
					align: (Align::Center, Align::Center),
					stroke: None,
					drop_shadow: Some((Color::BLACK.alpha(0xaa), (2, 2))),
				},
				&format!("#{}", i + 1),
			)
		})?;
		// }}}
		// {{{ Display chart name
		// Draw background
		let bg = &*NAME_BACKGROUND;
		drawer.blit_rbga(bottom_area, (0, 0), bg);

		// Draw text
		with_font(&EXO_FONT, |faces| {
			let initial_size = 24;
			let mut style = crate::bitmap::TextStyle {
				size: initial_size,
				weight: Some(800),
				color: Color::WHITE,
				align: (Align::Start, Align::Center),
				stroke: Some((Color::BLACK, 1.5)),
				drop_shadow: None,
			};

			while BitmapCanvas::plan_text_rendering((0, 0), faces, style, &song.title)?
				.1
				.width >= drawer.layout.width(bottom_in_area)
			{
				style.size -= 3;
				style.stroke = Some((
					Color::BLACK,
					style.size as f32 / (initial_size as f32) * 1.5,
				));
			}

			drawer.text(
				bottom_in_area,
				(0, drawer.layout.height(bottom_in_area) as i32 / 2),
				faces,
				style,
				&song.title,
			)
		})?;
		// }}}
		// {{{ Display jacket
		let jacket = chart.cached_jacket.as_ref().ok_or_else(|| {
			anyhow!(
				"Cannot find jacket for chart {} [{:?}]",
				song.title,
				chart.difficulty
			)
		})?;

		drawer.fill(jacket_with_border, Color::from_rgb_int(0x271E35));
		drawer.blit_rbg(jacket_area, (0, 0), jacket.bitmap);
		// }}}
		// {{{ Display difficulty background
		let diff_bg = get_difficulty_background(chart.difficulty);
		let diff_bg_area = Rect::from_image(diff_bg).align_whole(
			(Align::Center, Align::Center),
			(drawer.layout.width(jacket_with_border) as i32, 0),
		);

		drawer.blit_rbga(jacket_with_border, diff_bg_area.top_left(), diff_bg);
		// }}}
		// {{{ Display difficulty text
		let level_text = Level::LEVEL_STRINGS[chart.level.to_index()];
		let x_offset = if level_text.ends_with("+") {
			3
		} else if chart.level == Level::Eleven {
			-2
		} else {
			0
		};

		let diff_area_center = diff_bg_area.center();

		with_font(&EXO_FONT, |faces| {
			drawer.text(
				jacket_with_border,
				(diff_area_center.0 + x_offset, diff_area_center.1),
				faces,
				crate::bitmap::TextStyle {
					size: 25,
					weight: Some(600),
					color: Color::from_rgb_int(0xffffff),
					align: (Align::Center, Align::Center),
					stroke: None,
					drop_shadow: None,
				},
				level_text,
			)
		})?;
		// }}}
		// {{{ Display score background
		let score_bg = &*SCORE_BACKGROUND;
		let score_bg_pos = Rect::from_image(score_bg).align(
			(Align::End, Align::End),
			(
				drawer.layout.width(jacket_area) as i32,
				drawer.layout.height(jacket_area) as i32,
			),
		);

		drawer.blit_rbga(jacket_area, score_bg_pos, score_bg);
		// }}}
		// {{{ Display score text
		with_font(&EXO_FONT, |faces| {
			drawer.text(
				jacket_area,
				(
					score_bg_pos.0 + 5,
					score_bg_pos.1 + score_bg.height() as i32 / 2,
				),
				faces,
				crate::bitmap::TextStyle {
					size: 23,
					weight: Some(800),
					color: Color::WHITE,
					align: (Align::Start, Align::Center),
					stroke: Some((Color::BLACK, 1.5)),
					drop_shadow: None,
				},
				&format!("{:0>10}", format!("{}", play.score(scoring_system))),
			)
		})?;
		// }}}
		// {{{ Display status background
		let status_bg = &*STATUS_BACKGROUND;
		let status_bg_area = Rect::from_image(status_bg).align_whole(
			(Align::Center, Align::Center),
			(
				drawer.layout.width(jacket_area) as i32 + 3,
				drawer.layout.height(jacket_area) as i32 + 1,
			),
		);

		drawer.blit_rbga(jacket_area, status_bg_area.top_left(), status_bg);
		// }}}
		// {{{ Display status text
		with_font(&EXO_FONT, |faces| {
			let status = play.short_status(scoring_system, chart).ok_or_else(|| {
				anyhow!(
					"Could not get status for score {}",
					play.score(scoring_system)
				)
			})?;

			let x_offset = match status {
				'P' => 2,
				'M' => 2,
				// TODO: ensure the F is rendered properly as well
				_ => 0,
			};

			let center = status_bg_area.center();

			drawer.text(
				jacket_area,
				(center.0 + x_offset, center.1),
				faces,
				crate::bitmap::TextStyle {
					size: if status == 'M' { 30 } else { 36 },
					weight: Some(if status == 'M' { 800 } else { 500 }),
					color: Color::WHITE,
					align: (Align::Center, Align::Center),
					stroke: None,
					drop_shadow: None,
				},
				&format!("{}", status),
			)
		})?;
		// }}}
		// {{{ Display grade background
		let top_left_center = (drawer.layout.width(top_left_area) as i32 + jacket_margin) / 2;
		let grade_bg = &*GRADE_BACKGROUND;
		let grade_bg_area = Rect::from_image(grade_bg).align_whole(
			(Align::Center, Align::Center),
			(top_left_center, jacket_margin + 140),
		);

		drawer.blit_rbga(top_area, grade_bg_area.top_left(), grade_bg);
		// }}}
		// {{{ Display grade text
		with_font(&EXO_FONT, |faces| {
			let grade = play.score(scoring_system).grade();
			let center = grade_bg_area.center();

			drawer.text(
				top_left_area,
				(center.0, center.1),
				faces,
				crate::bitmap::TextStyle {
					size: 30,
					weight: Some(650),
					color: Color::from_rgb_int(0x203C6B),
					align: (Align::Center, Align::Center),
					stroke: Some((Color::WHITE, 1.5)),
					drop_shadow: None,
				},
				&format!("{}", grade),
			)
		})?;
		// }}}
		// {{{ Display rating text
		with_font(&EXO_FONT, |faces| -> Result<(), Error> {
			let mut style = crate::bitmap::TextStyle {
				size: 12,
				weight: Some(600),
				color: Color::WHITE,
				align: (Align::Center, Align::Center),
				stroke: None,
				drop_shadow: None,
			};

			drawer.text(
				top_left_area,
				(top_left_center, 73),
				faces,
				style,
				"POTENTIAL",
			)?;

			style.size = 25;
			style.weight = Some(700);

			drawer.text(
				top_left_area,
				(top_left_center, 94),
				faces,
				style,
				&format!(
					"{:.2}",
					play.play_rating_f32(scoring_system, chart.chart_constant)
				),
			)?;

			Ok(())
		})?;
		// }}}
		// {{{ Display ptt emblem
		let ptt_emblem = &*PTT_EMBLEM;
		drawer.blit_rbga(
			top_left_area,
			Rect::from_image(ptt_emblem)
				.align((Align::Center, Align::Center), (top_left_center, 115)),
			ptt_emblem,
		);
		// }}}
	}

	let mut out_buffer = Vec::new();
	let mut image = DynamicImage::ImageRgb8(
		ImageBuffer::from_raw(width, height, drawer.canvas.buffer.into_vec()).unwrap(),
	);

	debug_image_log(&image);

	if image.height() > 4096 {
		image = image.resize(4096, 4096, image::imageops::FilterType::Nearest);
	}

	let mut cursor = Cursor::new(&mut out_buffer);
	image.write_to(&mut cursor, image::ImageFormat::WebP)?;

	let reply = CreateReply::default()
		.attachment(CreateAttachment::bytes(out_buffer, "b30.png"))
		.content(format!(
			"Your ptt is {:.2}",
			rating_as_float(compute_b30_ptt(scoring_system, &plays))
		));
	ctx.send(reply).await?;

	Ok(())
}
// }}}
// {{{ B30
// {{{ Implementation
pub async fn b30_impl<C: MessageContext>(
	ctx: &mut C,
	scoring_system: Option<ScoringSystem>,
) -> Result<(), TaggedError> {
	let user = User::from_context(ctx)?;
	best_plays(ctx, &user, scoring_system.unwrap_or_default(), (5, 6), true).await?;
	Ok(())
}
// }}}
// {{{ Discord wrapper
/// Show the 30 best scores
#[poise::command(prefix_command, slash_command, user_cooldown = 30)]
pub async fn b30(
	mut ctx: PoiseContext<'_>,
	scoring_system: Option<ScoringSystem>,
) -> Result<(), Error> {
	let res = b30_impl(&mut ctx, scoring_system).await;
	ctx.handle_error(res).await?;
	Ok(())
}
// }}}
// }}}
// {{{ B-any
// {{{ Implementation
async fn bany_impl<C: MessageContext>(
	ctx: &mut C,
	scoring_system: Option<ScoringSystem>,
	width: u32,
	height: u32,
) -> Result<(), TaggedError> {
	let user = User::from_context(ctx)?;
	user.assert_is_pookie()?;
	best_plays(
		ctx,
		&user,
		scoring_system.unwrap_or_default(),
		(width, height),
		false,
	)
	.await?;

	Ok(())
}
// }}}
// {{{ Discord wrapper
#[poise::command(prefix_command, slash_command, hide_in_help, global_cooldown = 5)]
pub async fn bany(
	mut ctx: PoiseContext<'_>,
	scoring_system: Option<ScoringSystem>,
	width: u32,
	height: u32,
) -> Result<(), Error> {
	let res = bany_impl(&mut ctx, scoring_system, width, height).await;
	ctx.handle_error(res).await?;
	Ok(())
}
// }}}
// }}}
// {{{ Meta
// {{{ Implementation
async fn meta_impl<C: MessageContext>(ctx: &mut C) -> Result<(), TaggedError> {
	let user = User::from_context(ctx)?;
	let conn = ctx.data().db.get()?;
	let song_count: usize = conn
		.prepare_cached("SELECT count() as count FROM songs")?
		.query_row((), |row| row.get(0))?;

	let chart_count: usize = conn
		.prepare_cached("SELECT count() as count FROM charts")?
		.query_row((), |row| row.get(0))?;

	let users_count: usize = conn
		.prepare_cached("SELECT count() as count FROM users")?
		.query_row((), |row| row.get(0))?;

	let pookie_count: usize = conn
		.prepare_cached(
			"
        SELECT count() as count 
        FROM users 
        WHERE is_pookie=1
      ",
		)?
		.query_row((), |row| row.get(0))?;

	let play_count: usize = conn
		.prepare_cached("SELECT count() as count FROM plays")?
		.query_row((), |row| row.get(0))?;

	let your_play_count: usize = conn
		.prepare_cached(
			"
        SELECT count() as count 
        FROM plays 
        WHERE user_id=?
      ",
		)?
		.query_row([user.id], |row| row.get(0))?;

	let embed = CreateEmbed::default()
		.title("Bot statistics")
		.field("Songs", format!("{song_count}"), true)
		.field("Charts", format!("{chart_count}"), true)
		.field("Users", format!("{users_count}"), true)
		.field("Pookies", format!("{pookie_count}"), true)
		.field("Plays", format!("{play_count}"), true)
		.field("Your plays", format!("{your_play_count}"), true);

	ctx.send(CreateReply::default().reply(true).embed(embed))
		.await?;

	// TODO: remove once achivement system is implemented
	println!(
		"{:?}",
		GoalStats::make(ctx.data(), &user, ScoringSystem::Standard).await?
	);

	Ok(())
}
// }}}
// {{{ Discord wrapper
/// Show stats about the bot itself.
#[poise::command(prefix_command, slash_command, user_cooldown = 1)]
async fn meta(mut ctx: PoiseContext<'_>) -> Result<(), Error> {
	let res = meta_impl(&mut ctx).await;
	ctx.handle_error(res).await?;

	Ok(())
}
// }}}
// }}}