/*
    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::{
    ConnectionID,
    server::{AnnounceState, ConnectionData, Server},
};
use anyhow::Result;
use hurrycurry_locale::{TrError, tre, trm};
use hurrycurry_protocol::{
    GameConfig, KEEPALIVE_INTERVAL, Menu, Message, PacketC, PacketS, PlayerID, version,
};
use log::{debug, error, info, trace, warn};
use std::{
    collections::HashSet,
    time::{Duration, Instant},
};
use tokio::sync::{broadcast, mpsc};

impl Server {
    pub fn tick_outer(&mut self, dt: f32) -> anyhow::Result<()> {
        let mut keepalive_kick = Vec::new();
        let mut inactivity_kick = Vec::new();
        for (cid, conn) in &mut self.connections {
            conn.keepalive_timer += dt;
            if conn.keepalive_timer > KEEPALIVE_INTERVAL + 10. {
                keepalive_kick.push(*cid);
            }
            for pid in &conn.players {
                if let Some(t) = self.player_inactivity_timers.get_mut(pid) {
                    *t -= dt;
                    if *t < 0. {
                        inactivity_kick.push((*cid, *pid));
                    }
                }
            }
        }
        for cid in keepalive_kick {
            info!("{cid} Client did not send keepalive in time");
            self.disconnect(cid, Some(trm!("s.disconnect_reason.keepalive_timer")));
        }
        for (cid, pid) in inactivity_kick {
            info!("{cid} Removing {pid} because of inactivity");
            if let Some(conn) = self.connections.get(&cid) {
                let _ = conn.replies.try_send(PacketC::ServerMessage {
                    message: trm!("s.leave_inactivity"),
                    error: false,
                });
            }
            if let Err(e) = self.packet_in_outer(cid, PacketS::Leave { player: pid }) {
                warn!("error when auto-leaving player: {e}")
            }
        }

        if !self.paused {
            let start = Instant::now();
            let r = self.tick(dt);
            self.tick_perf.0 += start.elapsed();
            self.tick_perf.1 += 1;
            if self.tick_perf.1 >= 500 {
                debug!("tick perf {:?}", self.tick_perf.0 / 500);
                self.tick_perf = (Duration::ZERO, 0);
            }
            if let Some(config) = r {
                self.scoreboard.save()?;
                if let Err(e) = self.load(config) {
                    error!("Failed to load map: {e}");
                    self.packet_out.push_back(PacketC::ServerMessage {
                        message: e.into(),
                        error: true,
                    });
                }
            }
        }

        match &mut self.announce_state {
            AnnounceState::Queued if !self.paused => {
                self.announce_state = AnnounceState::Running(3.5);
                self.packet_out
                    .push_back(PacketC::Menu(Menu::AnnounceStart));
                self.update_paused();
            }
            AnnounceState::Running(timer) => {
                *timer -= dt;
                if *timer <= 0. {
                    self.announce_state = AnnounceState::Done;
                    self.update_paused();
                }
            }
            _ => (),
        }

        self.packet_out.extend(self.game.events.drain(..));
        self.packet_out.extend(self.vote_state.packet_out.drain(..));
        while let Some(p) = self.packet_out.pop_front() {
            if matches!(
                p,
                PacketC::UpdateMap { .. } | PacketC::Movement { .. } | PacketC::Debug(_)
            ) {
                trace!("-> {p:?}");
            } else {
                debug!("-> {p:?}");
            }
            let _ = self.broadcast.send(p);
        }
        Ok(())
    }

    pub async fn connect(
        &mut self,
        id: ConnectionID,
    ) -> (
        Vec<PacketC>,
        broadcast::Receiver<PacketC>,
        mpsc::Receiver<PacketC>,
    ) {
        let mut init = self.game.prime_client();
        let (replies_tx, replies_rx) = mpsc::channel(1024);
        let broadcast_rx = self.broadcast.subscribe();
        init.insert(0, version());
        init.insert(1, PacketC::ServerData(Box::new(*self.data.clone())));
        self.vote_state.prime_client(&mut init);
        self.connections.insert(
            id,
            ConnectionData {
                idle: false,
                ready: false,
                players: HashSet::new(),
                keepalive_timer: 0.,
                replies: replies_tx,
            },
        );
        self.update_paused();
        self.packet_out.push_back(PacketC::SpectatorCount {
            count: self.count_spectators(),
        });
        (init, broadcast_rx, replies_rx)
    }

    pub fn disconnect(&mut self, conn: ConnectionID, reason: Option<Message>) {
        if let Some(cd) = self.connections.get(&conn) {
            if let Some(reason) = reason {
                let _ = cd.replies.try_send(PacketC::Disconnect { reason });
            }
            for player in cd.players.clone() {
                let _ = self.packet_in_outer(conn, PacketS::Leave { player });
            }
            self.connections.remove(&conn);
            self.update_paused();
            self.packet_out.push_back(PacketC::SpectatorCount {
                count: self.count_spectators(),
            });
        }
    }

    pub fn packet_in_outer(
        &mut self,
        conn: ConnectionID,
        packet: PacketS,
    ) -> Result<Vec<PacketC>, TrError> {
        let Some(conn_data) = self.connections.get_mut(&conn) else {
            return Ok(vec![]);
        };
        if let Some(p) = get_packet_player(&packet)
            && !conn_data.players.contains(&p)
        {
            return Ok(vec![]);
        }
        let mut replies = Vec::new();
        match &packet {
            PacketS::Communicate {
                message: Some(Message::Text(text)),
                timeout: None,
                player,
                ..
            } if text.strip_prefix("/").is_some() => {
                // TODO: replace this by if let guard when stable
                match self.handle_command_parse(*player, text.strip_prefix("/").unwrap()) {
                    Ok(packets) => return Ok(packets),
                    Err(e) => {
                        return Ok(vec![PacketC::ServerMessage {
                            message: e.into(),
                            error: true,
                        }]);
                    }
                }
            }
            PacketS::Ready => {
                conn_data.ready = true;
                self.update_paused();
            }
            PacketS::Idle { paused } => {
                conn_data.idle = *paused;
                self.update_paused();
            }
            PacketS::Leave { player } => {
                self.last_movement_update.remove(player);
                self.player_inactivity_timers.remove(player);
                self.connections
                    .get_mut(&conn)
                    .unwrap()
                    .players
                    .remove(player);
                self.packet_out.push_back(PacketC::SpectatorCount {
                    count: self.count_spectators(),
                });
            }
            PacketS::Join { .. } => {
                if conn_data.players.len() > 8 {
                    return Err(tre!("s.error.conn_too_many_players"));
                }
            }
            PacketS::Keepalive => {
                conn_data.keepalive_timer = 0.;
            }
            PacketS::Movement {
                player, dir, boost, ..
            } if *boost || dir.length() > 0.5 => {
                self.player_inactivity_timers
                    .insert(*player, self.config.inactivity_timeout);
            }
            PacketS::Interact { player, .. } | PacketS::Communicate { player, .. } => {
                self.player_inactivity_timers
                    .insert(*player, self.config.inactivity_timeout);
            }
            _ => (),
        }

        let is_leave = matches!(packet, PacketS::Leave { .. });

        self.packet_in(&mut replies, packet)?;

        for p in &replies {
            if let PacketC::Joined { id } = p {
                self.connections.get_mut(&conn).unwrap().players.insert(*id);
                self.player_inactivity_timers
                    .insert(*id, self.config.inactivity_timeout);
                self.packet_out.push_back(PacketC::SpectatorCount {
                    count: self.count_spectators(),
                });
            }
        }

        if is_leave && self.count_chefs() == 0 {
            self.broadcast
                .send(PacketC::ServerMessage {
                    message: trm!("s.state.abort_no_players"),
                    error: false,
                })
                .ok();
            if let Err(e) = self.load(GameConfig {
                map: self.config.lobby.to_string(),
                ..Default::default()
            }) {
                error!("Failed to load lobby: {e}");
            }
        }
        Ok(replies)
    }

    pub fn update_paused(&mut self) {
        let all_idle = self
            .connections
            .values()
            .all(|c| c.idle && !c.players.is_empty());
        let mut not_ready = self
            .connections
            .values()
            .filter(|c| !c.ready && !c.players.is_empty())
            .count();
        let announcing = matches!(self.announce_state, AnnounceState::Running(_));

        if self.game.lobby {
            not_ready = 0; // not waiting for players in lobby
        }

        let should_pause = all_idle || not_ready > 0 || announcing;

        let reason = if not_ready > 0 {
            info!("Game paused: {not_ready} player are not ready");
            Some(trm!(
                "s.state.paused.any_not_ready",
                s = not_ready.to_string()
            ))
        } else if all_idle {
            if self.connections.is_empty() {
                info!("Game paused: Server empty");
            } else {
                info!("Game paused: All players idle");
            }
            Some(trm!("s.state.paused.all_idle"))
        } else if announcing {
            info!("Game paused: Waiting for announcement");
            None
        } else {
            if self.paused {
                info!("Game unpaused");
            }
            None
        };
        self.paused = should_pause;

        for p in self.game.players.keys() {
            self.packet_out
                .push_back(PacketC::Pause { state: self.paused });
            self.packet_out.push_back(PacketC::ServerHint {
                position: None,
                message: reason.clone(),
                player: *p,
            });
        }
    }
}

fn get_packet_player(packet: &PacketS) -> Option<PlayerID> {
    match packet {
        PacketS::Leave { player }
        | PacketS::Movement { player, .. }
        | PacketS::Interact { player, .. }
        | PacketS::Communicate { player, .. }
        | PacketS::ReplaceHand { player, .. }
        | PacketS::InitiateVote { player, .. }
        | PacketS::CastVote { player, .. } => Some(*player),
        PacketS::Join { .. }
        | PacketS::Idle { .. }
        | PacketS::Ready
        | PacketS::ApplyScore(_)
        | PacketS::Effect { .. }
        | PacketS::ReplayTick { .. }
        | PacketS::Debug(_)
        | PacketS::Keepalive => None,
    }
}
