use crate::multiplayer::GameConnection;
use std::{
    any::Any,
    error::Error,
    net::{TcpListener, TcpStream, ToSocketAddrs},
};
use tehuti::{
    engine::{EngineId, EngineMeetingEvent},
    event::{Duplex, Sender},
    protocol::ProtocolFrame,
};
use tehuti_socket::{TcpHost, TcpSession, TcpSessionResult};

pub struct TcpServerConnection {
    host: TcpHost,
    sessions: Vec<TcpSession>,
    events_sender: Option<Sender<EngineMeetingEvent>>,
}

impl Drop for TcpServerConnection {
    fn drop(&mut self) {
        if let Some(events_sender) = &self.events_sender {
            for session in &self.sessions {
                let engine_id = session.remote_engine_id();
                if let Err(error) =
                    events_sender.send(EngineMeetingEvent::UnregisterEngine { engine_id })
                {
                    tracing::event!(
                        target: "quaso::multiplayer::tcp::server",
                        tracing::Level::ERROR,
                        "Failed to send UnregisterEngine event during drop: {}",
                        error
                    );
                }
            }
        }
    }
}

impl TcpServerConnection {
    pub fn new(host: TcpHost) -> Self {
        Self {
            host,
            events_sender: None,
            sessions: Default::default(),
        }
    }

    pub fn make(listener: TcpListener) -> Result<Self, Box<dyn Error>> {
        Ok(Self::new(TcpHost::new(listener, EngineId::uuid())?))
    }

    pub fn listen(address: impl ToSocketAddrs) -> Result<Self, Box<dyn Error>> {
        Self::make(TcpListener::bind(address)?)
    }

    pub fn log_frames(mut self, value: bool) -> Self {
        self.host.log_frames = value;
        for session in &mut self.sessions {
            session.log_frames = value;
        }
        self
    }

    pub fn disconnect(&mut self, engine_id: EngineId) {
        self.sessions.retain(|session| {
            if session.remote_engine_id() == engine_id {
                tracing::event!(
                    target: "quaso::multiplayer::tcp::server",
                    tracing::Level::DEBUG,
                    "Disconnecting session: {:?}<->{:?}, remote engine ID: {:?}",
                    session.local_addr().unwrap(),
                    session.peer_addr().unwrap(),
                    session.remote_engine_id()
                );

                if let Some(events_sender) = &self.events_sender
                    && let Err(error) =
                        events_sender.send(EngineMeetingEvent::UnregisterEngine { engine_id })
                {
                    tracing::event!(
                        target: "quaso::multiplayer::tcp::server",
                        tracing::Level::ERROR,
                        "Failed to send UnregisterEngine event: {}",
                        error
                    );
                }
                false
            } else {
                true
            }
        });
    }
}

impl GameConnection for TcpServerConnection {
    fn on_register(&mut self, events_sender: &Sender<EngineMeetingEvent>) -> EngineId {
        self.events_sender = Some(events_sender.clone());
        self.host.local_engine_id()
    }

    fn on_unregister(&mut self, events_sender: &Sender<EngineMeetingEvent>) {
        for session in &self.sessions {
            let engine_id = session.remote_engine_id();
            tracing::event!(
                target: "quaso::multiplayer::tcp::server",
                tracing::Level::DEBUG,
                "Unregistering TCP server connection with remote engine ID {:?} at {:?}",
                engine_id,
                session.peer_addr().unwrap()
            );
            if let Err(error) =
                events_sender.send(EngineMeetingEvent::UnregisterEngine { engine_id })
            {
                tracing::event!(
                    target: "quaso::multiplayer::tcp::server",
                    tracing::Level::ERROR,
                    "Failed to send UnregisterEngine event: {}",
                    error
                );
            }
        }
        self.events_sender = None;
    }

    fn maintain(&mut self) -> bool {
        let mut result = true;
        loop {
            match self.host.accept() {
                Ok(Some(session_result)) => {
                    let TcpSessionResult { session, frames } = session_result;
                    tracing::event!(
                        target: "quaso::multiplayer::tcp::server",
                        tracing::Level::DEBUG,
                        "Accepted new TCP session from {:?} with remote engine ID {:?}",
                        session.peer_addr().unwrap(),
                        session.remote_engine_id()
                    );
                    if let Some(events_sender) = &self.events_sender {
                        let engine_id = session.remote_engine_id();
                        if let Err(error) = events_sender
                            .send(EngineMeetingEvent::RegisterEngine { engine_id, frames })
                        {
                            tracing::event!(
                                target: "quaso::multiplayer::tcp::server",
                                tracing::Level::ERROR,
                                "Failed to send RegisterEngine event: {}",
                                error
                            );
                        }
                        self.sessions.push(session);
                    }
                }
                Ok(None) => break,
                Err(error) => {
                    tracing::event!(
                        target: "quaso::multiplayer::tcp::server",
                        tracing::Level::ERROR,
                        "Failed to accept new TCP session: {}",
                        error
                    );
                    result = false;
                    break;
                }
            }
        }
        self.sessions.retain_mut(|session| {
            if let Err(error) = session.maintain() {
                tracing::event!(
                    target: "quaso::multiplayer::tcp::server",
                    tracing::Level::ERROR,
                    "Failed to maintain session (will be removed): {}",
                    error
                );
                if let Some(events_sender) = &self.events_sender {
                    let engine_id = session.remote_engine_id();
                    if let Err(error) =
                        events_sender.send(EngineMeetingEvent::UnregisterEngine { engine_id })
                    {
                        tracing::event!(
                            target: "quaso::multiplayer::tcp::server",
                            tracing::Level::ERROR,
                            "Failed to send UnregisterEngine event: {}",
                            error
                        );
                    }
                }
                false
            } else {
                true
            }
        });
        result
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

pub struct TcpClientConnection {
    session: TcpSession,
    frames: Duplex<ProtocolFrame>,
    events_sender: Option<Sender<EngineMeetingEvent>>,
}

impl Drop for TcpClientConnection {
    fn drop(&mut self) {
        if let Some(events_sender) = &self.events_sender {
            let engine_id = self.session.remote_engine_id();
            if let Err(error) =
                events_sender.send(EngineMeetingEvent::UnregisterEngine { engine_id })
            {
                tracing::event!(
                    target: "quaso::multiplayer::tcp::client",
                    tracing::Level::ERROR,
                    "Failed to send UnregisterEngine event during drop: {}",
                    error
                );
            }
        }
    }
}

impl TcpClientConnection {
    pub fn new(session_result: TcpSessionResult) -> Self {
        let TcpSessionResult { session, frames } = session_result;
        tracing::event!(
            target: "quaso::multiplayer::tcp::client",
            tracing::Level::DEBUG,
            "Established TCP session with remote engine ID {:?} at {:?}",
            session.remote_engine_id(),
            session.peer_addr()
        );
        Self {
            session,
            frames,
            events_sender: None,
        }
    }

    pub fn make(stream: TcpStream) -> Result<Self, Box<dyn Error>> {
        Ok(Self::new(TcpSession::make(stream, EngineId::uuid())?))
    }

    pub fn connect(address: impl ToSocketAddrs) -> Result<Self, Box<dyn Error>> {
        Self::make(TcpStream::connect(address)?)
    }

    pub fn log_frames(mut self, value: bool) -> Self {
        self.session.log_frames = value;
        self
    }
}

impl GameConnection for TcpClientConnection {
    fn on_register(&mut self, events_sender: &Sender<EngineMeetingEvent>) -> EngineId {
        let engine_id = self.session.remote_engine_id();
        tracing::event!(
            target: "quaso::multiplayer::tcp::client",
            tracing::Level::DEBUG,
            "Registering TCP client connection with remote engine ID {:?} at {:?}",
            engine_id,
            self.session.peer_addr().unwrap()
        );
        if let Err(error) = events_sender.send(EngineMeetingEvent::RegisterEngine {
            engine_id,
            frames: self.frames.clone(),
        }) {
            tracing::event!(
                target: "quaso::multiplayer::tcp::client",
                tracing::Level::ERROR,
                "Failed to send RegisterEngine event: {}",
                error
            );
        }
        self.events_sender = Some(events_sender.clone());
        engine_id
    }

    fn on_unregister(&mut self, events_sender: &Sender<EngineMeetingEvent>) {
        let engine_id = self.session.remote_engine_id();
        tracing::event!(
            target: "quaso::multiplayer::tcp::client",
            tracing::Level::DEBUG,
            "Unregistering TCP client connection with remote engine ID {:?} at {:?}",
            engine_id,
            self.session.peer_addr().unwrap()
        );
        if let Err(error) = events_sender.send(EngineMeetingEvent::UnregisterEngine { engine_id }) {
            tracing::event!(
                target: "quaso::multiplayer::tcp::client",
                tracing::Level::ERROR,
                "Failed to send UnregisterEngine event: {}",
                error
            );
        }
        self.events_sender = None;
    }

    fn maintain(&mut self) -> bool {
        if let Err(error) = self.session.maintain() {
            tracing::event!(
                target: "quaso::multiplayer::tcp::client",
                tracing::Level::ERROR,
                "Failed to maintain session: {}",
                error
            );
            false
        } else {
            true
        }
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}
