/* Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

use crate::binary::handlers;
use crate::shard::IggyShard;
use crate::streaming::session::Session;
use bytes::BytesMut;
use iggy_binary_protocol::RequestFrame;
use iggy_binary_protocol::codec::WireDecode;
use iggy_binary_protocol::codes::*;
use iggy_binary_protocol::requests::consumer_groups::*;
use iggy_binary_protocol::requests::consumer_offsets::*;
use iggy_binary_protocol::requests::messages::*;
use iggy_binary_protocol::requests::partitions::*;
use iggy_binary_protocol::requests::personal_access_tokens::*;
use iggy_binary_protocol::requests::segments::*;
use iggy_binary_protocol::requests::streams::*;
use iggy_binary_protocol::requests::system::*;
use iggy_binary_protocol::requests::topics::*;
use iggy_binary_protocol::requests::users::*;
use iggy_common::{
    Consumer, ConsumerKind, Identifier, IggyError, PollingKind, PollingStrategy, SenderKind,
};
use std::rc::Rc;
use tracing::{error, warn};

/// Result of handling a command. Most commands return `Finished`.
/// `SendMessages` may migrate the TCP connection to another shard.
pub enum HandlerResult {
    Finished,
    Migrated { to_shard: u16 },
}

/// Read the full payload from the sender into a buffer.
pub async fn read_payload(sender: &mut SenderKind, length: u32) -> Result<BytesMut, IggyError> {
    if length > MAX_CONTROL_FRAME_PAYLOAD {
        return Err(IggyError::InvalidCommand);
    }
    let mut buffer = BytesMut::with_capacity(length as usize);
    // SAFETY: when length > 0, sender.read() fills exactly `length` bytes
    // before returning Ok. On error the buffer is dropped without being read.
    // When length == 0, set_len(0) is a no-op (no uninitialized bytes exposed).
    unsafe {
        buffer.set_len(length as usize);
    }
    if length > 0 {
        let (result, buf) = sender.read(buffer).await;
        result?;
        buffer = buf;
    }
    Ok(buffer)
}

fn decode<T: WireDecode>(payload: &[u8]) -> Result<T, IggyError> {
    use iggy_binary_protocol::error::WireError;

    let (val, consumed) = T::decode(payload).map_err(|e| {
        warn!("wire decode error: {e}");
        match e {
            WireError::PayloadTooLarge { .. } => IggyError::InvalidSizeBytes,
            WireError::Validation(_) => IggyError::InvalidFormat,
            _ => IggyError::InvalidCommand,
        }
    })?;
    if consumed != payload.len() {
        warn!(
            "wire decode: {} trailing bytes (consumed {consumed}, payload {})",
            payload.len() - consumed,
            payload.len()
        );
    }
    Ok(val)
}

/// Convert a `WireIdentifier` to the domain `Identifier`.
pub fn wire_id_to_identifier(
    wire: &iggy_binary_protocol::WireIdentifier,
) -> Result<Identifier, IggyError> {
    match wire {
        iggy_binary_protocol::WireIdentifier::Numeric(id) => Identifier::numeric(*id),
        iggy_binary_protocol::WireIdentifier::String(name) => Identifier::named(name.as_str()),
    }
}

/// Convert a `WireConsumer` to the domain `Consumer`.
pub fn wire_consumer_to_consumer(
    wire: &iggy_binary_protocol::WireConsumer,
) -> Result<Consumer, IggyError> {
    let id = wire_id_to_identifier(&wire.id)?;
    let kind = ConsumerKind::from_code(wire.kind)?;
    Ok(Consumer { kind, id })
}

/// Convert a `WirePollingStrategy` to the domain `PollingStrategy`.
pub fn wire_polling_to_strategy(
    wire: &iggy_binary_protocol::WirePollingStrategy,
) -> Result<PollingStrategy, IggyError> {
    Ok(PollingStrategy {
        kind: PollingKind::from_code(wire.kind)?,
        value: wire.value,
    })
}

/// Maximum payload size for control-plane commands (non-SendMessages).
/// Prevents OOM from malicious clients sending `length = u32::MAX`.
/// SendMessages has its own size validation via `total_payload_size` checks.
pub const MAX_CONTROL_FRAME_PAYLOAD: u32 = 10 * 1024 * 1024; // 10 MB

/// Dispatch a SendMessages command with staged socket reads (zero-copy path).
///
/// Called by transport layers when the command code is `SEND_MESSAGES_CODE`.
/// The handler reads metadata, indexes, and messages directly from the socket
/// into separate `PooledBuffer`s for zero-copy partition append.
pub async fn dispatch_send_messages(
    sender: &mut SenderKind,
    payload_length: u32,
    session: &Session,
    shard: &Rc<IggyShard>,
) -> Result<HandlerResult, IggyError> {
    handlers::messages::send_messages_handler::handle_send_messages(
        sender,
        payload_length,
        session,
        shard,
    )
    .await
}

/// Central command dispatch for a decoded request frame.
///
/// Transport layers read the 8-byte header, validate via
/// `RequestFrame::payload_length()`, read the full payload, construct a
/// `RequestFrame::from_parts(code, frame.payload)`, and pass it here.
///
/// SendMessages is handled separately via `dispatch_send_messages()`.
#[allow(clippy::too_many_lines)]
pub async fn dispatch(
    frame: RequestFrame<'_>,
    sender: &mut SenderKind,
    session: &Session,
    shard: &Rc<IggyShard>,
) -> Result<HandlerResult, IggyError> {
    match frame.code {
        // System
        PING_CODE => {
            handlers::system::ping_handler::handle_ping(sender, session, shard).await
        }
        GET_STATS_CODE => {
            handlers::system::get_stats_handler::handle_get_stats(sender, session, shard).await
        }
        GET_ME_CODE => {
            handlers::system::get_me_handler::handle_get_me(sender, session, shard).await
        }
        GET_CLIENT_CODE => {
            let req: GetClientRequest = decode(frame.payload)?;
            handlers::system::get_client_handler::handle_get_client(req, sender, session, shard)
                .await
        }
        GET_CLIENTS_CODE => {
            handlers::system::get_clients_handler::handle_get_clients(sender, session, shard).await
        }
        GET_SNAPSHOT_FILE_CODE => {
            let req: GetSnapshotRequest = decode(frame.payload)?;
            handlers::system::get_snapshot_handler::handle_get_snapshot(
                req, sender, session, shard,
            )
            .await
        }
        GET_CLUSTER_METADATA_CODE => {
            handlers::cluster::get_cluster_metadata_handler::handle_get_cluster_metadata(
                sender, session, shard,
            )
            .await
        }

        // Streams
        GET_STREAM_CODE => {
            let req: GetStreamRequest = decode(frame.payload)?;
            handlers::streams::get_stream_handler::handle_get_stream(req, sender, session, shard)
                .await
        }
        GET_STREAMS_CODE => {
            handlers::streams::get_streams_handler::handle_get_streams(sender, session, shard)
                .await
        }
        CREATE_STREAM_CODE => {
            let req: CreateStreamRequest = decode(frame.payload)?;
            handlers::streams::create_stream_handler::handle_create_stream(
                req, sender, session, shard,
            )
            .await
        }
        DELETE_STREAM_CODE => {
            let req: DeleteStreamRequest = decode(frame.payload)?;
            handlers::streams::delete_stream_handler::handle_delete_stream(
                req, sender, session, shard,
            )
            .await
        }
        UPDATE_STREAM_CODE => {
            let req: UpdateStreamRequest = decode(frame.payload)?;
            handlers::streams::update_stream_handler::handle_update_stream(
                req, sender, session, shard,
            )
            .await
        }
        PURGE_STREAM_CODE => {
            let req: PurgeStreamRequest = decode(frame.payload)?;
            handlers::streams::purge_stream_handler::handle_purge_stream(
                req, sender, session, shard,
            )
            .await
        }

        // Topics
        GET_TOPIC_CODE => {
            let req: GetTopicRequest = decode(frame.payload)?;
            handlers::topics::get_topic_handler::handle_get_topic(req, sender, session, shard)
                .await
        }
        GET_TOPICS_CODE => {
            let req: GetTopicsRequest = decode(frame.payload)?;
            handlers::topics::get_topics_handler::handle_get_topics(req, sender, session, shard)
                .await
        }
        CREATE_TOPIC_CODE => {
            let req: CreateTopicRequest = decode(frame.payload)?;
            handlers::topics::create_topic_handler::handle_create_topic(
                req, sender, session, shard,
            )
            .await
        }
        DELETE_TOPIC_CODE => {
            let req: DeleteTopicRequest = decode(frame.payload)?;
            handlers::topics::delete_topic_handler::handle_delete_topic(
                req, sender, session, shard,
            )
            .await
        }
        UPDATE_TOPIC_CODE => {
            let req: UpdateTopicRequest = decode(frame.payload)?;
            handlers::topics::update_topic_handler::handle_update_topic(
                req, sender, session, shard,
            )
            .await
        }
        PURGE_TOPIC_CODE => {
            let req: PurgeTopicRequest = decode(frame.payload)?;
            handlers::topics::purge_topic_handler::handle_purge_topic(
                req, sender, session, shard,
            )
            .await
        }

        // Partitions
        CREATE_PARTITIONS_CODE => {
            let req: CreatePartitionsRequest = decode(frame.payload)?;
            handlers::partitions::create_partitions_handler::handle_create_partitions(
                req, sender, session, shard,
            )
            .await
        }
        DELETE_PARTITIONS_CODE => {
            let req: DeletePartitionsRequest = decode(frame.payload)?;
            handlers::partitions::delete_partitions_handler::handle_delete_partitions(
                req, sender, session, shard,
            )
            .await
        }

        // Segments
        DELETE_SEGMENTS_CODE => {
            let req: DeleteSegmentsRequest = decode(frame.payload)?;
            handlers::segments::delete_segments_handler::handle_delete_segments(
                req, sender, session, shard,
            )
            .await
        }

        // Messages (PollMessages + FlushUnsavedBuffer; SendMessages handled above)
        POLL_MESSAGES_CODE => {
            let req: PollMessagesRequest = decode(frame.payload)?;
            handlers::messages::poll_messages_handler::handle_poll_messages(
                req, sender, session, shard,
            )
            .await
        }
        FLUSH_UNSAVED_BUFFER_CODE => {
            let req: FlushUnsavedBufferRequest = decode(frame.payload)?;
            handlers::messages::flush_unsaved_buffer_handler::handle_flush_unsaved_buffer(
                req, sender, session, shard,
            )
            .await
        }

        // Consumer Offsets
        GET_CONSUMER_OFFSET_CODE => {
            let req: GetConsumerOffsetRequest = decode(frame.payload)?;
            handlers::consumer_offsets::get_consumer_offset_handler::handle_get_consumer_offset(
                req, sender, session, shard,
            )
            .await
        }
        STORE_CONSUMER_OFFSET_CODE => {
            let req: StoreConsumerOffsetRequest = decode(frame.payload)?;
            handlers::consumer_offsets::store_consumer_offset_handler::handle_store_consumer_offset(
                req, sender, session, shard,
            )
            .await
        }
        DELETE_CONSUMER_OFFSET_CODE => {
            let req: DeleteConsumerOffsetRequest = decode(frame.payload)?;
            handlers::consumer_offsets::delete_consumer_offset_handler::handle_delete_consumer_offset(
                req, sender, session, shard,
            )
            .await
        }

        // Consumer Groups
        GET_CONSUMER_GROUP_CODE => {
            let req: GetConsumerGroupRequest = decode(frame.payload)?;
            handlers::consumer_groups::get_consumer_group_handler::handle_get_consumer_group(
                req, sender, session, shard,
            )
            .await
        }
        GET_CONSUMER_GROUPS_CODE => {
            let req: GetConsumerGroupsRequest = decode(frame.payload)?;
            handlers::consumer_groups::get_consumer_groups_handler::handle_get_consumer_groups(
                req, sender, session, shard,
            )
            .await
        }
        CREATE_CONSUMER_GROUP_CODE => {
            let req: CreateConsumerGroupRequest = decode(frame.payload)?;
            handlers::consumer_groups::create_consumer_group_handler::handle_create_consumer_group(
                req, sender, session, shard,
            )
            .await
        }
        DELETE_CONSUMER_GROUP_CODE => {
            let req: DeleteConsumerGroupRequest = decode(frame.payload)?;
            handlers::consumer_groups::delete_consumer_group_handler::handle_delete_consumer_group(
                req, sender, session, shard,
            )
            .await
        }
        JOIN_CONSUMER_GROUP_CODE => {
            let req: JoinConsumerGroupRequest = decode(frame.payload)?;
            handlers::consumer_groups::join_consumer_group_handler::handle_join_consumer_group(
                req, sender, session, shard,
            )
            .await
        }
        LEAVE_CONSUMER_GROUP_CODE => {
            let req: LeaveConsumerGroupRequest = decode(frame.payload)?;
            handlers::consumer_groups::leave_consumer_group_handler::handle_leave_consumer_group(
                req, sender, session, shard,
            )
            .await
        }

        // Users
        GET_USER_CODE => {
            let req: GetUserRequest = decode(frame.payload)?;
            handlers::users::get_user_handler::handle_get_user(req, sender, session, shard).await
        }
        GET_USERS_CODE => {
            handlers::users::get_users_handler::handle_get_users(sender, session, shard).await
        }
        CREATE_USER_CODE => {
            let req: CreateUserRequest = decode(frame.payload)?;
            handlers::users::create_user_handler::handle_create_user(req, sender, session, shard)
                .await
        }
        DELETE_USER_CODE => {
            let req: DeleteUserRequest = decode(frame.payload)?;
            handlers::users::delete_user_handler::handle_delete_user(req, sender, session, shard)
                .await
        }
        UPDATE_USER_CODE => {
            let req: UpdateUserRequest = decode(frame.payload)?;
            handlers::users::update_user_handler::handle_update_user(req, sender, session, shard)
                .await
        }
        UPDATE_PERMISSIONS_CODE => {
            let req: UpdatePermissionsRequest = decode(frame.payload)?;
            handlers::users::update_permissions_handler::handle_update_permissions(
                req, sender, session, shard,
            )
            .await
        }
        CHANGE_PASSWORD_CODE => {
            let req: ChangePasswordRequest = decode(frame.payload)?;
            handlers::users::change_password_handler::handle_change_password(
                req, sender, session, shard,
            )
            .await
        }
        LOGIN_USER_CODE => {
            let req: LoginUserRequest = decode(frame.payload)?;
            handlers::users::login_user_handler::handle_login_user(req, sender, session, shard)
                .await
        }
        LOGOUT_USER_CODE => {
            handlers::users::logout_user_handler::handle_logout_user(sender, session, shard).await
        }

        // Personal Access Tokens
        GET_PERSONAL_ACCESS_TOKENS_CODE => {
            handlers::personal_access_tokens::get_personal_access_tokens_handler::handle_get_personal_access_tokens(
                sender, session, shard,
            )
            .await
        }
        CREATE_PERSONAL_ACCESS_TOKEN_CODE => {
            let req: CreatePersonalAccessTokenRequest = decode(frame.payload)?;
            handlers::personal_access_tokens::create_personal_access_token_handler::handle_create_personal_access_token(
                req, sender, session, shard,
            )
            .await
        }
        DELETE_PERSONAL_ACCESS_TOKEN_CODE => {
            let req: DeletePersonalAccessTokenRequest = decode(frame.payload)?;
            handlers::personal_access_tokens::delete_personal_access_token_handler::handle_delete_personal_access_token(
                req, sender, session, shard,
            )
            .await
        }
        LOGIN_WITH_PERSONAL_ACCESS_TOKEN_CODE => {
            let req: LoginWithPersonalAccessTokenRequest = decode(frame.payload)?;
            handlers::personal_access_tokens::login_with_personal_access_token_handler::handle_login_with_personal_access_token(
                req, sender, session, shard,
            )
            .await
        }

        _ => {
            error!("Unknown command code: {}", frame.code);
            Err(IggyError::InvalidCommand)
        }
    }
}
