use std::sync::OnceLock;

use wicked_waifus_commons::time_util;
use wicked_waifus_network::{config::ServiceEndPoint, ServiceListener, ServiceMessage};
use wicked_waifus_protocol::{
    message::Message, CreateCharacterResponse, EnterGameResponse, ErrorCode, Protobuf,
};
use wicked_waifus_protocol_internal::{
    CreatePlayerDataResponse, ForwardClientMessagePush, MessageID, StartPlayerSessionResponse,
};

use crate::session::SessionManager;

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("Failed to bind ServiceListener")]
    BindFailed,
}

pub async fn start_task(
    listen_end_point: &ServiceEndPoint,
    session_mgr: &'static SessionManager,
) -> Result<(), Error> {
    static IS_STARTED: OnceLock<()> = OnceLock::new();
    if IS_STARTED.set(()).is_err() {
        tracing::error!("service_message_handler: task already started");
        return Ok(());
    }

    let listener = ServiceListener::bind(listen_end_point)
        .await
        .map_err(|_| Error::BindFailed)?;

    tokio::spawn(async move { handler_task_fn(listener, session_mgr).await });
    Ok(())
}

async fn handler_task_fn(mut listener: ServiceListener, session_mgr: &'static SessionManager) {
    loop {
        let Some(message) = listener.receive().await else {
            continue;
        };

        tracing::debug!(
            "received message from service: {}, rpc_id: {} message_id: {}",
            message.src_service_id,
            message.rpc_id,
            message.message_id
        );

        match message.message_id {
            CreatePlayerDataResponse::MESSAGE_ID => {
                on_create_player_data_response(message, session_mgr).await
            }
            StartPlayerSessionResponse::MESSAGE_ID => {
                on_start_player_session_response(message, session_mgr).await
            }
            ForwardClientMessagePush::MESSAGE_ID => {
                on_forward_client_message_push(message, session_mgr).await
            }
            unhandled => tracing::warn!(
                "unhandled service message id: {unhandled}, from service_id: {}",
                message.src_service_id
            ),
        }
    }
}

async fn on_forward_client_message_push(
    message: ServiceMessage,
    session_mgr: &'static SessionManager,
) {
    let Ok(push) = ForwardClientMessagePush::decode(message.data.as_ref()) else {
        tracing::error!(
            "failed to decode ForwardClientMessagePush, data: {}",
            hex::encode(&message.data)
        );
        return;
    };

    let Ok(message) = Message::decode(&push.data) else {
        tracing::error!(
            "ForwardClientMessage: failed to decode underlying message data, session_id: {}",
            push.gateway_session_id
        );
        return;
    };

    let Some(mut session) = session_mgr.get_mut(push.gateway_session_id) else {
        tracing::error!(
            "ForwardClientMessage: session not found, id: {}",
            push.gateway_session_id
        );
        return;
    };

    tracing::debug!(
        "forward message from game server to client, message_id: {}, session_id: {}",
        message.get_message_id(),
        push.gateway_session_id
    );

    let _ = session.send_message(message).await.inspect_err(|err| {
        tracing::error!("ForwardClientMessage: failed to send message, error: {err}")
    });
}

async fn on_start_player_session_response(
    message: ServiceMessage,
    session_mgr: &'static SessionManager,
) {
    let Ok(response) = StartPlayerSessionResponse::decode(message.data.as_ref()) else {
        tracing::error!(
            "failed to decode StartPlayerSessionResponse, data: {}",
            hex::encode(&message.data)
        );
        return;
    };

    let Some(mut session) = session_mgr.get_mut(response.gateway_session_id) else {
        tracing::error!(
            "StartPlayerSession: session not found, id: {}",
            response.gateway_session_id
        );
        return;
    };

    if response.code != 0 {
        tracing::debug!(
            "StartPlayerSession failed, session_id: {}, player_id: {:?}, user_id: {:?}, error code: {}",
            response.gateway_session_id,
            &session.player_id,
            &session.user_id,
            response.code
        );

        let _ = session
            .send_response(
                EnterGameResponse {
                    error_code: response.code,
                    ..Default::default()
                },
                message.rpc_id,
            )
            .await;
    } else {
        tracing::debug!(
            "StartPlayerSession success, game server should forward subsequent messages now. player_id: {}",
            session.player_id.unwrap_or_default()
        );
    }
}

async fn on_create_player_data_response(
    message: ServiceMessage,
    session_mgr: &'static SessionManager,
) {
    let Ok(response) = CreatePlayerDataResponse::decode(message.data.as_ref()) else {
        tracing::error!(
            "failed to decode CreatePlayerDataResponse, data: {}",
            hex::encode(&message.data)
        );
        return;
    };

    let Some(mut session) = session_mgr.get_mut(response.session_id) else {
        tracing::debug!("session with id {} not found", response.session_id);
        return;
    };

    if response.code != 0 {
        tracing::warn!("CreatePlayerData failed, code: {}", response.code);

        let _ = session
            .send_response(
                CreateCharacterResponse {
                    error_code: response.code,
                    ..Default::default()
                },
                message.rpc_id,
            )
            .await;
        return;
    }

    let Some(user_id) = session.user_id.as_ref() else {
        tracing::debug!(
            "session.user_id is None, session_id: {}",
            response.session_id
        );
        return;
    };

    tracing::info!(
        "CreateCharacter success, player_id: {}, user_id: {}",
        response.player_id,
        user_id
    );
    session.player_id = Some(response.player_id);

    let _ = session
        .send_response(
            CreateCharacterResponse {
                error_code: ErrorCode::Success.into(),
                player_id: response.player_id,
                name: response.name,
                create_time: time_util::unix_timestamp() as i32,
            },
            message.rpc_id,
        )
        .await;
}
