//
// A wrapper around https://git.estrogen.zone/dumbswitch.git to provide nice space api responses
// Copyright (C) 2025  memdmp
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License along with this program.  If not, see .
//
use std::env;
use std::io::Error;
use std::net::SocketAddr;
use std::sync::LazyLock;
use std::time::Duration;
use http_body_util::{BodyExt, Empty, Full};
use hyper::Uri;
use hyper::body::Incoming;
use hyper::header::HeaderValue;
use hyper::{Request, Response, body::Bytes, server::conn::http1, service::service_fn};
use hyper_util::rt::TokioIo;
use serde_json::json;
use tokio::net::TcpStream;
use tokio::{net::TcpListener, sync::RwLock, task, time};
static RAW_JSON: &'static str = include_str!("../api.json");
static PARSED_JSON: LazyLock> =
  LazyLock::new(|| RwLock::new(serde_json::from_str(RAW_JSON).expect("JSON parse error")));
type StdResult = std::result::Result;
type Result = StdResult>;
async fn respond(_: Request) -> Result>> {
  let str = PARSED_JSON.read().await.to_string();
  let len = str.len();
  let mut res = Response::new(Full::new(Bytes::from(str)));
  res
    .headers_mut()
    .append("Content-Type", HeaderValue::from_static("application/json"));
  let len = HeaderValue::from_str(&len.to_string());
  if len.is_ok() {
    res.headers_mut().append("Content-Length", len.unwrap());
  }
  Ok(res)
}
async fn fetch_url(url: hyper::Uri) -> Result> {
  if url.scheme_str() != Some("http") {
    return Err(Box::new(Error::new(
      std::io::ErrorKind::InvalidInput,
      "This function only works with HTTP URIs.",
    )));
  }
  let host = url.host().expect("uri has no host");
  let port = url.port_u16().unwrap_or(80);
  let addr = format!("{}:{}", host, port);
  let stream = TcpStream::connect(addr).await?;
  let io = TokioIo::new(stream);
  let (mut sender, conn) = hyper::client::conn::http1::handshake(io).await?;
  tokio::task::spawn(async move {
    if let Err(err) = conn.await {
      println!("Connection failed: {:?}", err);
    }
  });
  let authority = url.authority().unwrap().clone();
  let path = url.path();
  let req = Request::builder()
    .uri(path)
    .header(hyper::header::HOST, authority.as_str())
    .body(Empty::::new())?;
  Ok(sender.send_request(req).await?)
}
fn get_request_target_uri() -> Uri {
  env::var("REQUEST_TO")
    .unwrap_or("http://10.0.0.77:8080/".to_string())
    .parse::()
    .unwrap()
}
async fn get_is_open() -> Result {
  let mut res = fetch_url(get_request_target_uri()).await?;
  let mut out = "".to_string();
  while let Some(next) = res.frame().await {
    let frame = next?;
    if let Some(chunk) = frame.data_ref() {
      out = format!("{out}{}", std::str::from_utf8(chunk).unwrap());
    }
  }
  let state = out.trim().starts_with("a");
  Ok(state)
}
#[tokio::main]
async fn main() -> Result<()> {
  {
    let state = {
      let mut parsed_json = PARSED_JSON.write().await;
      if !parsed_json.is_object() {
        panic!("api.json must have top-level object!");
      }
      let parsed_json_obj = parsed_json.as_object_mut().unwrap();
      let state = parsed_json_obj.get_mut("state");
      let state = if state.is_some() {
        state.unwrap()
      } else {
        &mut json!("{}")
      };
      let state_obj = state.as_object_mut().unwrap();
      state_obj.remove_entry("open");
      state.clone()
    };
    // ensure state is present
    let mut parsed_json = PARSED_JSON.write().await;
    let parsed_json_obj = parsed_json.as_object_mut().unwrap();
    parsed_json_obj.insert("state".to_string(), state);
  }
  task::spawn(async {
    loop {
      {
        let is_open = get_is_open().await;
        let mut parsed_json = PARSED_JSON.write().await;
        let state = parsed_json.get_mut("state");
        if state.is_none() {
          panic!("State is none!");
        };
        let state = state.unwrap().as_object_mut();
        if state.is_none() {
          panic!("State was turned into non-object!");
        }
        let state = state.unwrap();
        if is_open.is_ok() {
          let is_open = is_open.unwrap();
          state.insert("open".to_string(), serde_json::Value::Bool(is_open));
        } else {
          state.remove("open");
          eprintln!("Failed to fetch open status: {:#?}", is_open.unwrap_err());
        }
      }
      time::sleep(Duration::from_secs(1)).await;
    }
  });
  let addr: SocketAddr = env::var("LISTEN_ON")
    .unwrap_or("127.0.0.1:3000".to_string())
    .parse()?;
  // We create a TcpListener and bind it to 127.0.0.1:3000
  let listener = TcpListener::bind(addr).await?;
  println!(
    "Listening on: LISTEN_ON={addr:#?}
Sending fetch requests to: REQUEST_TO={:#?}",
    get_request_target_uri()
  );
  // We start a loop to continuously accept incoming connections
  loop {
    let (stream, _) = listener.accept().await?;
    // Use an adapter to access something implementing `tokio::io` traits as if they implement
    // `hyper::rt` IO traits.
    let io = TokioIo::new(stream);
    // Spawn a tokio task to serve multiple connections concurrently
    tokio::task::spawn(async move {
      // Finally, we bind the incoming connection to our `hello` service
      if let Err(err) = http1::Builder::new()
        // `service_fn` converts our function in a `Service`
        .serve_connection(io, service_fn(respond))
        .await
      {
        eprintln!("Error serving connection: {:?}", err);
      }
    });
  }
}