/*
    Hurry Curry! - a game about cooking
    Copyright (C) 2026 Hurry Curry! Contributors

    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, version 3 of the License only.

    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 <https://www.gnu.org/licenses/>.

*/
use crate::{MAX_SERVERS, Registry, conn_test::test_connect};
use anyhow::Result;
use hurrycurry_protocol::registry::Submission;
use log::{debug, info};
use rocket::{State, http::hyper::Uri, post, serde::json::Json};
use std::{
    net::{IpAddr, SocketAddr},
    str::FromStr,
    sync::Arc,
    time::Instant,
};
use tokio::{net::lookup_host, sync::RwLock};

#[post("/v1/register", data = "<submission>")]
pub(super) async fn r_register(
    client_addr: IpAddr,
    registry: &State<Arc<RwLock<Registry>>>,
    submission: Json<Submission>,
) -> Result<&'static str, &'static str> {
    match register_inner(client_addr, registry, &submission).await {
        Ok(()) => {
            info!("ok");
            Ok("ok")
        }
        Err(e) => {
            info!("err: {e}");
            Err(e)
        }
    }
}

async fn register_inner(
    client_addr: IpAddr,
    registry: &Arc<RwLock<Registry>>,
    submission: &Submission,
) -> Result<(), &'static str> {
    debug!("submission {submission:?} from {client_addr}");
    let client_addr = client_addr.to_canonical();
    let uri = Uri::from_str(&submission.uri).map_err(|_| "invalid uri")?;

    let scheme = uri.scheme().ok_or("no scheme")?.as_str();
    let secure = match scheme {
        "ws" => false,
        "wss" => true,
        _ => return Err("invalid scheme"),
    };
    let host = uri.host().ok_or("no host")?;
    let port = uri.port_u16().unwrap_or(if secure { 443 } else { 27032 });

    let (saddr, uri_q) = match IpAddr::from_str(host) {
        Ok(mut addr) => {
            if addr.is_unspecified() {
                addr = client_addr;
            }
            if addr.is_loopback() {
                return Err("loopback address");
            }
            if addr.is_multicast() {
                return Err("multicast address");
            }
            info!("{client_addr} != {addr}");
            let addr = addr.to_canonical();
            if client_addr == addr {
                let saddr = SocketAddr::new(addr, port);
                (saddr, format!("{scheme}://{saddr}"))
            } else {
                return Err("source address does not match uri");
            }
        }
        Err(_) => {
            if let Some(addr) = lookup_host(format!("{host}:0"))
                .await
                .map_err(|_| "dns lookup failed")?
                .find(|a| a.ip().to_canonical() == client_addr)
            {
                (
                    SocketAddr::new(addr.ip().to_canonical(), port),
                    format!("{scheme}://{host}:{port}"),
                )
            } else {
                return Err("host verification failed");
            }
        }
    };

    test_connect(saddr, &uri_q).await?;

    let mut g = registry.write().await;

    if g.servers.len() > MAX_SERVERS {
        return Err("too many registered servers");
    }
    info!("submission approved for {uri_q:?}");
    let entry = g.servers.entry(submission.secret).or_default();
    entry.name = submission.name.clone();
    entry.players_online = submission.players;
    entry.last_game = submission.last_game;
    entry.version = submission.version;
    entry.address.insert(uri_q, Instant::now());

    Ok(())
}
