// This file is part of hnefatafl-copenhagen.
//
// hnefatafl-copenhagen 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.
//
// hnefatafl-copenhagen 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 std::{
    collections::{HashMap, VecDeque},
    fmt::{self, Write},
    str::FromStr,
    sync::mpsc::Sender,
};

use rust_i18n::t;
use serde::{Deserialize, Serialize};

use crate::{
    Id,
    board::{Board, BoardSize},
    game::Game,
    glicko::Rating,
    play::{PlayRecordTimed, Plays},
    rating::Rated,
    role::Role,
    status::Status,
    time::{Time, TimeSettings},
};

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ArchivedGame {
    pub id: Id,
    pub attacker: String,
    pub attacker_rating: Rating,
    pub defender: String,
    pub defender_rating: Rating,
    pub rated: Rated,
    pub plays: Plays,
    pub status: Status,
    pub texts: VecDeque<String>,
    pub board_size: BoardSize,
}

impl ArchivedGame {
    #[must_use]
    pub fn new(game: ServerGame, attacker_rating: Rating, defender_rating: Rating) -> Self {
        Self {
            id: game.id,
            attacker: game.attacker,
            attacker_rating,
            defender: game.defender,
            defender_rating,
            rated: game.rated,
            plays: game.game.plays,
            status: game.game.status,
            texts: game.texts,
            board_size: game.game.board.size(),
        }
    }
}

impl fmt::Display for ArchivedGame {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        writeln!(
            f,
            "{}: {}, {}: {} {}, {}: {} {}, {}: {}",
            t!("ID"),
            self.id,
            t!("Attacker"),
            self.attacker,
            self.attacker_rating.to_string_rounded(),
            t!("Defender"),
            self.defender,
            self.defender_rating.to_string_rounded(),
            t!("Size"),
            self.board_size,
        )
    }
}

impl PartialEq for ArchivedGame {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

impl Eq for ArchivedGame {}

#[derive(Clone, Debug)]
pub struct Messenger(Option<Sender<String>>);

impl Messenger {
    #[must_use]
    pub fn new(sender: Sender<String>) -> Self {
        Self(Some(sender))
    }

    pub fn send(&self, string: String) {
        if let Some(sender) = &self.0 {
            let _ok = sender.send(string);
        }
    }
}

#[derive(Clone, Debug)]
pub struct ServerGame {
    pub id: Id,
    pub attacker: String,
    pub attacker_tx: Messenger,
    pub defender: String,
    pub defender_tx: Messenger,
    pub draw_requested: Role,
    pub elapsed_time: i64,
    pub rated: Rated,
    pub game: Game,
    pub texts: VecDeque<String>,
}

impl From<ServerGameSerialized> for ServerGame {
    fn from(game: ServerGameSerialized) -> Self {
        Self {
            id: game.id,
            attacker: game.attacker,
            attacker_tx: Messenger(None),
            defender: game.defender,
            defender_tx: Messenger(None),
            draw_requested: Role::Roleless,
            elapsed_time: 0,
            rated: game.rated,
            game: game.game,
            texts: game.texts,
        }
    }
}

impl ServerGame {
    #[must_use]
    pub fn protocol(&self) -> String {
        format!(
            "game {} {} {} {}",
            self.id, self.attacker, self.defender, self.rated
        )
    }

    #[allow(clippy::missing_panics_doc)]
    #[must_use]
    pub fn new(
        attacker_tx: Option<Sender<String>>,
        defender_tx: Option<Sender<String>>,
        game: ServerGameLight,
    ) -> Self {
        let (Some(attacker), Some(defender)) = (game.attacker, game.defender) else {
            unreachable!();
        };

        let plays = match game.timed {
            TimeSettings::Timed(time) => Plays::PlayRecordsTimed(vec![PlayRecordTimed {
                play: None,
                attacker_time: time.into(),
                defender_time: time.into(),
            }]),
            TimeSettings::UnTimed => Plays::PlayRecords(vec![None]),
        };

        let board = Board::new(game.board_size);

        Self {
            id: game.id,
            attacker,
            attacker_tx: Messenger(attacker_tx),
            defender,
            defender_tx: Messenger(defender_tx),
            draw_requested: Role::Roleless,
            elapsed_time: 0,
            rated: game.rated,
            game: Game {
                attacker_time: game.timed.clone(),
                defender_time: game.timed,
                board,
                plays,
                ..Game::default()
            },
            texts: VecDeque::new(),
        }
    }
}

impl fmt::Display for ServerGame {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}: {}, {}, {} ",
            self.id, self.attacker, self.defender, self.rated
        )
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ServerGameSerialized {
    pub id: Id,
    pub attacker: String,
    pub defender: String,
    pub rated: Rated,
    pub game: Game,
    pub texts: VecDeque<String>,
    pub timed: TimeSettings,
}

impl From<&ServerGame> for ServerGameSerialized {
    fn from(game: &ServerGame) -> Self {
        Self {
            id: game.id,
            attacker: game.attacker.clone(),
            defender: game.defender.clone(),
            rated: game.rated,
            game: game.game.clone(),
            texts: game.texts.clone(),
            timed: TimeSettings::default(),
        }
    }
}

#[derive(Clone, Debug, Default)]
pub struct ServerGames(pub HashMap<Id, ServerGame>);

#[derive(Clone, Default, Eq, PartialEq)]
pub struct Challenger(pub Option<String>);

impl fmt::Debug for Challenger {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(challenger) = &self.0 {
            write!(f, "{challenger}")?;
        } else {
            write!(f, "_")?;
        }

        Ok(())
    }
}

impl fmt::Display for Challenger {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "challenger: ")?;
        if let Some(challenger) = &self.0 {
            write!(f, "{challenger}")?;
        } else {
            write!(f, "none")?;
        }

        Ok(())
    }
}

#[derive(Clone, Eq, PartialEq)]
pub struct ServerGameLight {
    pub id: Id,
    pub attacker: Option<String>,
    pub defender: Option<String>,
    pub challenger: Challenger,
    pub rated: Rated,
    pub timed: TimeSettings,
    pub spectators: HashMap<String, usize>,
    pub challenge_accepted: bool,
    pub game_over: bool,
    pub board_size: BoardSize,
}

impl ServerGameLight {
    #[must_use]
    pub fn new(
        game_id: Id,
        username: String,
        rated: Rated,
        timed: TimeSettings,
        board_size: BoardSize,
        role: Role,
    ) -> Self {
        if role == Role::Attacker {
            Self {
                id: game_id,
                attacker: Some(username),
                defender: None,
                challenger: Challenger::default(),
                rated,
                timed,
                board_size,
                spectators: HashMap::new(),
                challenge_accepted: false,
                game_over: false,
            }
        } else {
            Self {
                id: game_id,
                attacker: None,
                defender: Some(username),
                challenger: Challenger::default(),
                rated,
                timed,
                board_size,
                spectators: HashMap::new(),
                challenge_accepted: false,
                game_over: false,
            }
        }
    }

    #[must_use]
    pub fn spectators(&self) -> Vec<usize> {
        let mut ids = Vec::new();

        for (name, id) in &self.spectators {
            if Some(name) != self.attacker.as_ref() && Some(name) != self.defender.as_ref() {
                ids.push(*id);
            }
        }

        ids
    }
}

impl From<&ServerGameSerialized> for ServerGameLight {
    fn from(game: &ServerGameSerialized) -> Self {
        Self {
            id: game.id,
            attacker: Some(game.attacker.clone()),
            defender: Some(game.defender.clone()),
            challenger: Challenger::default(),
            rated: game.rated,
            timed: game.timed.clone(),
            board_size: game.game.board.size(),
            spectators: HashMap::new(),
            challenge_accepted: true,
            game_over: false,
        }
    }
}

impl fmt::Debug for ServerGameLight {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let attacker = if let Some(name) = &self.attacker {
            name
        } else {
            "_"
        };

        let defender = if let Some(name) = &self.defender {
            name
        } else {
            "_"
        };

        let Ok(spectators) = ron::ser::to_string(&self.spectators) else {
            unreachable!();
        };

        write!(
            f,
            "game {} {attacker} {defender} {} {:?} {} {:?} {} {spectators}",
            self.id,
            self.rated,
            self.timed,
            self.board_size,
            self.challenger,
            self.challenge_accepted,
        )
    }
}

impl fmt::Display for ServerGameLight {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let attacker = t!("attacker");
        let defender = t!("defender");
        let rated = t!(self.rated.to_string());
        let none = t!("none");

        let attacker = if let Some(name) = &self.attacker {
            &format!("{attacker}: {name}")
        } else {
            &format!("{attacker}: {none}")
        };

        let defender = if let Some(name) = &self.defender {
            &format!("{defender}: {name}")
        } else {
            &format!("{defender}: {none}")
        };

        write!(
            f,
            "# {}\n{attacker}, {defender}, {rated}\n{}: {}, {}: {}",
            self.id,
            t!("time"),
            self.timed,
            t!("board size"),
            self.board_size,
        )
    }
}

impl TryFrom<&[&str]> for ServerGameLight {
    type Error = anyhow::Error;

    fn try_from(vector: &[&str]) -> anyhow::Result<Self> {
        if vector.len() < 12 {
            return Err(anyhow::Error::msg("ServerGameLight has too few words."));
        }

        let id = vector[1];
        let attacker = vector[2];
        let defender = vector[3];
        let rated = vector[4];
        let timed = vector[5];
        let minutes = vector[6];
        let add_seconds = vector[7];
        let board_size = vector[8];
        let challenger = vector[9];
        let challenge_accepted = vector[10];
        let spectators = vector[11];

        let id = id.parse::<Id>()?;

        let attacker = if attacker == "_" {
            None
        } else {
            Some(attacker.to_string())
        };

        let defender = if defender == "_" {
            None
        } else {
            Some(defender.to_string())
        };

        let timed = match timed {
            "fischer" => TimeSettings::Timed(Time {
                add_seconds: add_seconds.parse::<i64>()?,
                milliseconds_left: minutes.parse::<i64>()?,
            }),
            // "un-timed"
            _ => TimeSettings::UnTimed,
        };

        let board_size = BoardSize::from_str(board_size)?;

        let Ok(challenge_accepted) = <bool as FromStr>::from_str(challenge_accepted) else {
            return Err(anyhow::Error::msg("challenge_accepted is not a bool."));
        };

        let spectators = ron::from_str(spectators)?;

        let mut game = Self {
            id,
            attacker,
            defender,
            challenger: Challenger::default(),
            rated: Rated::from_str(rated)?,
            timed,
            board_size,
            spectators,
            challenge_accepted,
            game_over: false,
        };

        if challenger != "_" {
            game.challenger.0 = Some(challenger.to_string());
        }

        Ok(game)
    }
}

#[derive(Clone, Default, Eq, PartialEq)]
pub struct ServerGamesLight(pub HashMap<Id, ServerGameLight>);

impl fmt::Debug for ServerGamesLight {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for game in self.0.values().filter(|game| !game.game_over) {
            write!(f, "{game:?} ")?;
        }

        Ok(())
    }
}

#[derive(Clone, Default, Eq, PartialEq)]
pub struct ServerGamesLightVec(pub Vec<ServerGameLight>);

impl ServerGamesLightVec {
    #[allow(clippy::missing_errors_doc)]
    pub fn display_games(&self, username: Option<&str>) -> anyhow::Result<String> {
        let mut output = String::new();

        for game in &self.0 {
            if !game.game_over {
                write!(output, "{game:?} ")?;
            } else if let Some(username) = username
                && game.spectators.contains_key(username)
            {
                write!(output, "{game:?} ")?;
            }
        }

        Ok(output)
    }
}

impl fmt::Debug for ServerGamesLightVec {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for game in self.0.iter().filter(|game| !game.game_over) {
            write!(f, "{game:?} ")?;
        }

        Ok(())
    }
}