use anyhow::{Context, Result};
use axum::{extract::connect_info::Connected, serve::IncomingStream};
use libservice::{Service, ToService};
use service_webpage::WebpageService;
use std::{net::SocketAddr, sync::Arc};
use tokio::net::TcpListener;
use tracing::{error, info};

use crate::CmdContext;

#[derive(Debug, clap::Args)]
pub struct ServeArgs {
	/// IP and port to bind to
	/// Should look like `127.0.0.1:3030`
	addr: String,
}

impl ServeArgs {
	pub async fn run(self, _ctx: CmdContext) -> Result<()> {
		let state = Arc::new(RouterState {});

		#[expect(clippy::expect_used)]
		let app = make_service(Some(state.clone()))
			.await
			.context("while building service")?
			.make_router()
			.expect("service must be initialized")
			.into_make_service_with_connect_info::<ServerConnectInfo>();

		let listener = match tokio::net::TcpListener::bind(self.addr.clone()).await {
			Ok(x) => x,
			Err(error) => {
				match error.kind() {
					std::io::ErrorKind::AddrInUse => {
						error!(
							message = "Cannot bind to address, already in use",
							server_addr = self.addr
						);
					}
					_ => {
						error!(message = "Error while starting server", ?error);
					}
				}

				std::process::exit(1);
			}
		};

		match listener.local_addr() {
			Ok(x) => info!("listening on http://{x}"),
			Err(error) => {
				error!(message = "Could not determine local address", ?error);
				std::process::exit(1);
			}
		}

		axum::serve(listener, app)
			.await
			.context("in server thread")?;

		return Ok(());
	}
}

//
// MARK: state
//

#[derive(Debug, clap::Args, Clone)]
pub struct RouterState {}

/// If state is none, dry-init
pub async fn make_service(_state: Option<Arc<RouterState>>) -> Result<impl ToService> {
	let service_webpage = WebpageService::new();

	Ok(Service::new().merge(service_webpage).to_service().trace())
}

#[derive(Clone, Debug)]
pub struct ServerConnectInfo {
	#[expect(dead_code)]
	pub addr: Arc<SocketAddr>,
}

impl Connected<IncomingStream<'_, TcpListener>> for ServerConnectInfo {
	fn connect_info(target: IncomingStream<'_, TcpListener>) -> Self {
		let addr = target.remote_addr();

		Self {
			addr: Arc::new(*addr),
		}
	}
}
