use crate::cmd::config::{populate_system, resolve_config, ConfigArgsRef};
use crate::cmd::CliContext;
use axum::http::header;
use axum::http::{StatusCode, Uri};
use clap::Parser;
use eternaltwin_core::types::WeakError;
use eternaltwin_system::EternaltwinSystem;
use std::net::SocketAddr;
use std::str::FromStr;
use tokio::net::TcpListener;

/// Arguments to the `start` task.
#[derive(Debug, Parser)]
pub struct Args {
  #[clap(flatten)]
  config: crate::cmd::config::Args,
}

impl ConfigArgsRef for Args {
  fn config_args(&self) -> &crate::cmd::config::Args {
    &self.config
  }
}

pub async fn run(cx: CliContext<'_, Args>) -> Result<(), WeakError> {
  let config = resolve_config(&cx, true).await?;
  let system = EternaltwinSystem::create_dyn(&config, cx.out)
    .await
    .map_err(WeakError::wrap)?;

  eprintln!("loaded internal Eternaltwin system");

  populate_system(&system, &config, true).await?;

  eprintln!("initialization complete");

  let mut listen_addr: SocketAddr = match SocketAddr::from_str(&config.backend.listen.value) {
    Ok(addr) => addr,
    Err(e) => {
      eprintln!("invalid listen address {}", &config.backend.listen.value);
      return Err(WeakError::wrap(e));
    }
  };
  if let Some(port) = config.backend.port.value {
    listen_addr.set_port(port);
  }

  let backend = eternaltwin_rest::app(system).fallback(fallback);

  eprintln!("started server at http://localhost:{}/", listen_addr.port());

  let listener = TcpListener::bind(listen_addr).await.unwrap();
  axum::serve(listener, backend).await.unwrap();

  Ok(())
}

async fn fallback(uri: Uri) -> (StatusCode, [(header::HeaderName, &'static str); 2], Vec<u8>) {
  const INDEX: &str = "index.html";

  let path = uri.path();
  let path = path.strip_prefix('/').unwrap_or(path);
  let file = if path == INDEX {
    None
  } else {
    eternaltwin_app::BROWSER.get_file(path)
  };
  let (media_type, file) = match file {
    None => {
      let index = match eternaltwin_app::BROWSER.get_file(INDEX) {
        None => {
          eprintln!("<-> GET {} 1ms Not Found", uri.path(),);
          return (
            StatusCode::INTERNAL_SERVER_ERROR,
            [
              (header::X_CONTENT_TYPE_OPTIONS, "nosniff"),
              (header::CONTENT_TYPE, "text/plain; charset=utf-8"),
            ],
            "Not Found".to_string().into_bytes(),
          );
        }
        Some(index) => index,
      };
      ("text/html; charset=utf-8", index)
    }
    Some(file) => {
      // See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types>
      let extension = match path.rfind('.').map(|idx| &path[idx..]) {
        Some(".css") => "text/css; charset=utf-8",
        Some(".gif") => "image/gif",
        Some(".ico") => "image/vnd.microsoft.icon",
        Some(".jpg") => "image/jpeg",
        Some(".jpeg") => "image/jpeg",
        Some(".js") => "text/javascript; charset=utf-8",
        Some(".png") => "image/png",
        Some(".svg") => "image/svg+xml; charset=utf-8",
        Some(".woff2") => "font/woff2",
        ext => {
          if let Some(ext) = ext {
            eprintln!("unknown file extension: {ext}");
          }
          "application/octet-stream"
        }
      };
      (extension, file)
    }
  };
  eprintln!("<-> GET {} 1ms OK", uri.path(),);
  (
    StatusCode::OK,
    [
      (header::X_CONTENT_TYPE_OPTIONS, "nosniff"),
      (header::CONTENT_TYPE, media_type),
    ],
    file.contents().to_vec(),
  )
}
