//! KV service implementation

use crate::conversions::make_header;
use crate::proto::{
    compare, kv_server::Kv, DeleteRangeRequest, DeleteRangeResponse, PutRequest, PutResponse,
    RangeRequest, RangeResponse, ResponseOp, TxnRequest, TxnResponse,
};
use chainfire_raft::core::RaftCore;
use chainfire_types::command::RaftCommand;
use std::sync::Arc;
use tonic::{Request, Response, Status};
use tracing::{debug, trace, warn};

/// KV service implementation
pub struct KvServiceImpl {
    /// Raft core for consensus
    raft: Arc<RaftCore>,
    /// Cluster ID
    cluster_id: u64,
}

impl KvServiceImpl {
    /// Create a new KV service
    pub fn new(raft: Arc<RaftCore>, cluster_id: u64) -> Self {
        Self { raft, cluster_id }
    }

    /// Create a response header
    async fn make_header(&self, revision: u64) -> crate::proto::ResponseHeader {
        let term = self.raft.current_term().await;
        make_header(self.cluster_id, self.raft.node_id(), revision, term)
    }
}

#[tonic::async_trait]
impl Kv for KvServiceImpl {
    async fn range(
        &self,
        request: Request<RangeRequest>,
    ) -> Result<Response<RangeResponse>, Status> {
        let req = request.into_inner();
        trace!(key = ?String::from_utf8_lossy(&req.key), serializable = req.serializable, "Range request");

        // For linearizable reads (serializable=false), verify we're reading consistent state
        // NOTE: Custom RaftCore doesn't yet support linearizable_read() method
        // For now, just warn if non-serializable read is requested
        if !req.serializable {
            warn!("Linearizable reads not yet supported in custom Raft, performing serializable read");
        }

        // Get state machine from Raft core
        let sm = self.raft.state_machine();

        let entries = if req.range_end.is_empty() {
            // Single key lookup
            sm.kv()
                .get(&req.key)
                .map_err(|e| Status::internal(e.to_string()))?
                .into_iter()
                .collect()
        } else {
            // Range scan
            sm.kv()
                .range(&req.key, Some(&req.range_end))
                .map_err(|e| Status::internal(e.to_string()))?
        };

        let revision = sm.current_revision();
        let kvs: Vec<_> = entries.into_iter().map(Into::into).collect();
        let count = kvs.len() as i64;

        Ok(Response::new(RangeResponse {
            header: Some(self.make_header(revision).await),
            kvs,
            more: false,
            count,
        }))
    }

    async fn put(&self, request: Request<PutRequest>) -> Result<Response<PutResponse>, Status> {
        let req = request.into_inner();
        debug!(key = ?String::from_utf8_lossy(&req.key), "Put request");

        let command = RaftCommand::Put {
            key: req.key,
            value: req.value,
            lease_id: if req.lease != 0 { Some(req.lease) } else { None },
            prev_kv: req.prev_kv,
        };

        // Write through custom Raft
        self.raft
            .client_write(command)
            .await
            .map_err(|e| Status::internal(format!("Raft write failed: {:?}", e)))?;

        // Get current revision after write
        let revision = self.raft.last_applied().await;

        // NOTE: Custom RaftCore doesn't yet return prev_kv from writes
        if req.prev_kv {
            warn!("prev_kv not yet supported in custom Raft implementation");
        }

        Ok(Response::new(PutResponse {
            header: Some(self.make_header(revision).await),
            prev_kv: None, // Not supported yet in custom RaftCore
        }))
    }

    async fn delete(
        &self,
        request: Request<DeleteRangeRequest>,
    ) -> Result<Response<DeleteRangeResponse>, Status> {
        let req = request.into_inner();
        debug!(key = ?String::from_utf8_lossy(&req.key), "Delete request");

        // Workaround: Pre-check key existence to determine deleted count
        // TODO: Replace with proper RaftResponse.deleted once client_write returns full response
        let sm = self.raft.state_machine();
        let deleted_count = if req.range_end.is_empty() {
            // Single key delete - check if exists
            let exists = sm.kv()
                .get(&req.key)
                .map_err(|e| Status::internal(e.to_string()))?
                .is_some();
            if exists { 1 } else { 0 }
        } else {
            // Range delete - count keys in range
            let kvs = sm.kv()
                .range(&req.key, Some(&req.range_end))
                .map_err(|e| Status::internal(e.to_string()))?;
            kvs.len() as i64
        };

        let command = if req.range_end.is_empty() {
            RaftCommand::Delete {
                key: req.key,
                prev_kv: req.prev_kv,
            }
        } else {
            RaftCommand::DeleteRange {
                start: req.key,
                end: req.range_end,
                prev_kv: req.prev_kv,
            }
        };

        // Write through custom Raft
        self.raft
            .client_write(command)
            .await
            .map_err(|e| Status::internal(format!("Raft write failed: {:?}", e)))?;

        // Get current revision after write
        let revision = self.raft.last_applied().await;

        // NOTE: prev_kv not yet supported in custom RaftCore
        if req.prev_kv {
            warn!("prev_kv not yet supported in custom Raft implementation");
        }

        Ok(Response::new(DeleteRangeResponse {
            header: Some(self.make_header(revision).await),
            deleted: deleted_count,
            prev_kvs: vec![], // Not supported yet
        }))
    }

    async fn txn(&self, request: Request<TxnRequest>) -> Result<Response<TxnResponse>, Status> {
        let req = request.into_inner();
        debug!("Txn request with {} comparisons", req.compare.len());

        // Convert protobuf types to internal types
        let compare: Vec<_> = req
            .compare
            .into_iter()
            .map(|c| {
                use chainfire_types::command::{
                    Compare, CompareResult as InternalResult, CompareTarget as InternalTarget,
                };

                let result = match compare::CompareResult::try_from(c.result) {
                    Ok(compare::CompareResult::Equal) => InternalResult::Equal,
                    Ok(compare::CompareResult::NotEqual) => InternalResult::NotEqual,
                    Ok(compare::CompareResult::Greater) => InternalResult::Greater,
                    Ok(compare::CompareResult::Less) => InternalResult::Less,
                    Err(_) => InternalResult::Equal,
                };

                let target = match c.target_union {
                    Some(compare::TargetUnion::Version(v)) => InternalTarget::Version(v as u64),
                    Some(compare::TargetUnion::CreateRevision(v)) => {
                        InternalTarget::CreateRevision(v as u64)
                    }
                    Some(compare::TargetUnion::ModRevision(v)) => {
                        InternalTarget::ModRevision(v as u64)
                    }
                    Some(compare::TargetUnion::Value(v)) => InternalTarget::Value(v),
                    None => InternalTarget::Version(0),
                };

                Compare {
                    key: c.key,
                    target,
                    result,
                }
            })
            .collect();

        let success = convert_ops(&req.success);
        let failure = convert_ops(&req.failure);

        let command = RaftCommand::Txn {
            compare,
            success,
            failure,
        };

        // Write through custom Raft
        self.raft
            .client_write(command)
            .await
            .map_err(|e| Status::internal(format!("Raft write failed: {:?}", e)))?;

        // Get current revision after write
        let revision = self.raft.last_applied().await;

        // NOTE: Custom RaftCore doesn't yet return transaction response details
        warn!("Transaction response details not yet supported in custom Raft implementation");

        Ok(Response::new(TxnResponse {
            header: Some(self.make_header(revision).await),
            succeeded: true, // Assume success if no error
            responses: vec![], // Not supported yet
        }))
    }
}

/// Convert internal TxnOpResponse to proto ResponseOp
fn convert_txn_responses(
    responses: &[chainfire_types::command::TxnOpResponse],
    revision: u64,
) -> Vec<ResponseOp> {
    use crate::proto::response_op::Response as ProtoResponse;
    use chainfire_types::command::TxnOpResponse;

    responses
        .iter()
        .map(|resp| {
            let response = match resp {
                TxnOpResponse::Put { prev_kv } => ProtoResponse::ResponsePut(PutResponse {
                    header: Some(make_header(0, 0, revision, 0)),
                    prev_kv: prev_kv.clone().map(Into::into),
                }),
                TxnOpResponse::Delete { deleted, prev_kvs } => {
                    ProtoResponse::ResponseDeleteRange(DeleteRangeResponse {
                        header: Some(make_header(0, 0, revision, 0)),
                        deleted: *deleted as i64,
                        prev_kvs: prev_kvs.iter().cloned().map(Into::into).collect(),
                    })
                }
                TxnOpResponse::Range { kvs, count, more } => {
                    ProtoResponse::ResponseRange(RangeResponse {
                        header: Some(make_header(0, 0, revision, 0)),
                        kvs: kvs.iter().cloned().map(Into::into).collect(),
                        count: *count as i64,
                        more: *more,
                    })
                }
            };
            ResponseOp {
                response: Some(response),
            }
        })
        .collect()
}

fn convert_ops(
    ops: &[crate::proto::RequestOp],
) -> Vec<chainfire_types::command::TxnOp> {
    use chainfire_types::command::TxnOp;

    ops.iter()
        .filter_map(|op| {
            op.request.as_ref().map(|req| match req {
                crate::proto::request_op::Request::RequestPut(put) => TxnOp::Put {
                    key: put.key.clone(),
                    value: put.value.clone(),
                    lease_id: if put.lease != 0 { Some(put.lease) } else { None },
                },
                crate::proto::request_op::Request::RequestDeleteRange(del) => {
                    if del.range_end.is_empty() {
                        TxnOp::Delete {
                            key: del.key.clone(),
                        }
                    } else {
                        TxnOp::DeleteRange {
                            start: del.key.clone(),
                            end: del.range_end.clone(),
                        }
                    }
                }
                crate::proto::request_op::Request::RequestRange(range) => TxnOp::Range {
                    key: range.key.clone(),
                    range_end: range.range_end.clone(),
                    limit: range.limit,
                    keys_only: range.keys_only,
                    count_only: range.count_only,
                }
            })
        })
        .collect()
}
