/*
    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::server::Server;
use anyhow::{Result, bail};
use hurrycurry_protocol::{VERSION, registry::Submission};
use log::{debug, error, info, warn};
use rand::random;
use reqwest::{Client, Url, header::USER_AGENT};
use std::{
    net::{IpAddr, Ipv4Addr, Ipv6Addr},
    str::FromStr,
    sync::Arc,
    time::Duration,
};
use tokio::{sync::RwLock, time::interval};

pub struct Register {
    name: String,
    port: u16,
    register_uri: Option<String>,
    registry_uri: String,
    state: Arc<RwLock<Server>>,
    ip4_client: Option<Client>,
    ip6_client: Option<Client>,
    secret: u128,
    players: usize,
}

impl Register {
    pub fn new(
        name: String,
        port: u16,
        register_uri: Option<String>,
        registry_uri: String,
        state: Arc<RwLock<Server>>,
        no4: bool,
        no6: bool,
    ) -> Self {
        Self {
            name,
            register_uri,
            registry_uri,
            players: 0,
            port,
            secret: random(),
            state,
            ip4_client: if no4 {
                None
            } else {
                Some(
                    Client::builder()
                        .local_address(IpAddr::V4(Ipv4Addr::UNSPECIFIED))
                        .build()
                        .unwrap(),
                )
            },
            ip6_client: if no6 {
                None
            } else {
                Some(
                    Client::builder()
                        .local_address(IpAddr::V6(Ipv6Addr::UNSPECIFIED))
                        .build()
                        .unwrap(),
                )
            },
        }
    }
    pub async fn register_loop(mut self) {
        let mut interval = interval(Duration::from_secs(60));
        loop {
            interval.tick().await;
            self.players = self.state.read().await.count_chefs();
            if let Err(e) = self.register().await {
                warn!("register error: {e}")
            }
        }
    }

    pub async fn register(&self) -> Result<()> {
        debug!("register update");
        let [v4, v6] = [&self.ip4_client, &self.ip6_client].map(|client| async {
            if let Some(r) = client.as_ref().map(|client| {
                self.register_with(
                    client,
                    self.register_uri
                        .clone()
                        .unwrap_or(format!("ws://0.0.0.0:{}", self.port)),
                )
            }) {
                Some(r.await)
            } else {
                None
            }
        });
        match tokio::join!(v4, v6) {
            (None, None) => info!("no registration sent"),
            (Some(Ok(())), None) => info!("Registration successful (IPv4)"),
            (None, Some(Ok(()))) => info!("Registration successful (IPv6)"),
            (Some(Ok(())), Some(Ok(()))) => info!("Registration successful (IPv4 + IPv6)"),
            (Some(Err(e)), None) => error!("Registration failed (IPv4): {e}"),
            (None, Some(Err(e))) => error!("Registration failed (IPv6): {e}"),
            (Some(Err(e1)), Some(Err(e2))) => {
                error!("Registration failed (IPv4 + IPv6): {e1}, {e2}")
            }
            (Some(Ok(())), Some(Err(e))) => {
                warn!("Registration partially failed (IPv4 ok, IPv6 fail): {e}")
            }
            (Some(Err(e)), Some(Ok(()))) => {
                warn!("Registration partially failed (IPv4 fail, IPv6 ok): {e}")
            }
        }
        Ok(())
    }
    // TODO ip v6
    pub async fn register_with(&self, client: &Client, uri: String) -> Result<()> {
        let res = client
            .post(Url::from_str(&format!("{}/v1/register", self.registry_uri)).unwrap())
            .header(
                USER_AGENT,
                format!("hurrycurry-server {}", env!("CARGO_PKG_VERSION")),
            )
            .json(&Submission {
                last_game: 0,
                name: self.name.clone(),
                uri,
                players: self.players,
                secret: self.secret,
                version: *VERSION,
            })
            .send()
            .await?;

        let r = res.text().await?;
        if r == "ok" {
            Ok(())
        } else {
            bail!("{r}");
        }
    }
}
