// -------------------------------------------------------------------------------------------------
//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
//  https://nautechsystems.io
//
//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
//  You may not use this file except in compliance with the License.
//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
//
//  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 nautilus_model::{
    data::{
        FundingRateUpdate, IndexPriceUpdate, InstrumentClose, InstrumentStatus, MarkPriceUpdate,
        OptionGreekValues, OptionGreeks, QuoteTick, TradeTick,
    },
    identifiers::TradeId,
};

use super::{
    super::{SbeCursor, SbeDecodeError, SbeEncodeError, SbeWriter},
    MarketSbeMessage,
    common::{
        DECIMAL_BLOCK_LENGTH, PRICE_BLOCK_LENGTH, QUANTITY_BLOCK_LENGTH, decode_aggressor_side,
        decode_decimal, decode_greeks_convention, decode_instrument_close_type,
        decode_instrument_id, decode_market_status_action, decode_optional_bool,
        decode_optional_ustr, decode_price, decode_quantity, decode_unix_nanos, encode_decimal,
        encode_instrument_id, encode_optional_bool, encode_optional_ustr, encode_price,
        encode_quantity, encode_unix_nanos, encode_var_string16, encoded_instrument_id_size,
        encoded_optional_ustr_size, encoded_var_string16_size,
    },
    template_id,
};

impl MarketSbeMessage for QuoteTick {
    const TEMPLATE_ID: u16 = template_id::QUOTE_TICK;
    const BLOCK_LENGTH: u16 = (PRICE_BLOCK_LENGTH * 2) + (QUANTITY_BLOCK_LENGTH * 2) + 16;

    fn encode_body(&self, writer: &mut SbeWriter<'_>) -> Result<(), SbeEncodeError> {
        encode_price(writer, &self.bid_price);
        encode_price(writer, &self.ask_price);
        encode_quantity(writer, &self.bid_size);
        encode_quantity(writer, &self.ask_size);
        encode_unix_nanos(writer, self.ts_event);
        encode_unix_nanos(writer, self.ts_init);
        encode_instrument_id(writer, &self.instrument_id)
    }

    fn decode_body(cursor: &mut SbeCursor<'_>) -> Result<Self, SbeDecodeError> {
        let bid_price = decode_price(cursor)?;
        let ask_price = decode_price(cursor)?;
        let bid_size = decode_quantity(cursor)?;
        let ask_size = decode_quantity(cursor)?;
        let ts_event = decode_unix_nanos(cursor)?;
        let ts_init = decode_unix_nanos(cursor)?;
        let instrument_id = decode_instrument_id(cursor)?;

        Ok(Self {
            instrument_id,
            bid_price,
            ask_price,
            bid_size,
            ask_size,
            ts_event,
            ts_init,
        })
    }

    fn encoded_body_size(&self) -> usize {
        usize::from(Self::BLOCK_LENGTH) + encoded_instrument_id_size(&self.instrument_id)
    }
}

impl MarketSbeMessage for TradeTick {
    const TEMPLATE_ID: u16 = template_id::TRADE_TICK;
    const BLOCK_LENGTH: u16 = PRICE_BLOCK_LENGTH + QUANTITY_BLOCK_LENGTH + 17;

    fn encode_body(&self, writer: &mut SbeWriter<'_>) -> Result<(), SbeEncodeError> {
        encode_price(writer, &self.price);
        encode_quantity(writer, &self.size);
        writer.write_u8(self.aggressor_side as u8);
        encode_unix_nanos(writer, self.ts_event);
        encode_unix_nanos(writer, self.ts_init);
        encode_instrument_id(writer, &self.instrument_id)?;
        encode_var_string16(writer, "TradeTick.trade_id", self.trade_id.as_str())
    }

    fn decode_body(cursor: &mut SbeCursor<'_>) -> Result<Self, SbeDecodeError> {
        let price = decode_price(cursor)?;
        let size = decode_quantity(cursor)?;
        let aggressor_side = decode_aggressor_side(cursor)?;
        let ts_event = decode_unix_nanos(cursor)?;
        let ts_init = decode_unix_nanos(cursor)?;
        let instrument_id = decode_instrument_id(cursor)?;
        let trade_id = TradeId::new(cursor.read_var_string16_ref()?);

        Ok(Self {
            instrument_id,
            price,
            size,
            aggressor_side,
            trade_id,
            ts_event,
            ts_init,
        })
    }

    fn encoded_body_size(&self) -> usize {
        usize::from(Self::BLOCK_LENGTH)
            + encoded_instrument_id_size(&self.instrument_id)
            + encoded_var_string16_size(self.trade_id.as_str())
    }
}

impl MarketSbeMessage for MarkPriceUpdate {
    const TEMPLATE_ID: u16 = template_id::MARK_PRICE_UPDATE;
    const BLOCK_LENGTH: u16 = PRICE_BLOCK_LENGTH + 16;

    fn encode_body(&self, writer: &mut SbeWriter<'_>) -> Result<(), SbeEncodeError> {
        encode_price(writer, &self.value);
        encode_unix_nanos(writer, self.ts_event);
        encode_unix_nanos(writer, self.ts_init);
        encode_instrument_id(writer, &self.instrument_id)
    }

    fn decode_body(cursor: &mut SbeCursor<'_>) -> Result<Self, SbeDecodeError> {
        let value = decode_price(cursor)?;
        let ts_event = decode_unix_nanos(cursor)?;
        let ts_init = decode_unix_nanos(cursor)?;
        let instrument_id = decode_instrument_id(cursor)?;

        Ok(Self {
            instrument_id,
            value,
            ts_event,
            ts_init,
        })
    }

    fn encoded_body_size(&self) -> usize {
        usize::from(Self::BLOCK_LENGTH) + encoded_instrument_id_size(&self.instrument_id)
    }
}

impl MarketSbeMessage for IndexPriceUpdate {
    const TEMPLATE_ID: u16 = template_id::INDEX_PRICE_UPDATE;
    const BLOCK_LENGTH: u16 = PRICE_BLOCK_LENGTH + 16;

    fn encode_body(&self, writer: &mut SbeWriter<'_>) -> Result<(), SbeEncodeError> {
        encode_price(writer, &self.value);
        encode_unix_nanos(writer, self.ts_event);
        encode_unix_nanos(writer, self.ts_init);
        encode_instrument_id(writer, &self.instrument_id)
    }

    fn decode_body(cursor: &mut SbeCursor<'_>) -> Result<Self, SbeDecodeError> {
        let value = decode_price(cursor)?;
        let ts_event = decode_unix_nanos(cursor)?;
        let ts_init = decode_unix_nanos(cursor)?;
        let instrument_id = decode_instrument_id(cursor)?;

        Ok(Self {
            instrument_id,
            value,
            ts_event,
            ts_init,
        })
    }

    fn encoded_body_size(&self) -> usize {
        usize::from(Self::BLOCK_LENGTH) + encoded_instrument_id_size(&self.instrument_id)
    }
}

impl MarketSbeMessage for FundingRateUpdate {
    const TEMPLATE_ID: u16 = template_id::FUNDING_RATE_UPDATE;
    const BLOCK_LENGTH: u16 = DECIMAL_BLOCK_LENGTH + 26;

    fn encode_body(&self, writer: &mut SbeWriter<'_>) -> Result<(), SbeEncodeError> {
        encode_decimal(writer, &self.rate);
        writer.write_u16_le(self.interval.unwrap_or(u16::MAX));
        writer.write_u64_le(self.next_funding_ns.map_or(u64::MAX, |value| *value));
        encode_unix_nanos(writer, self.ts_event);
        encode_unix_nanos(writer, self.ts_init);
        encode_instrument_id(writer, &self.instrument_id)
    }

    fn decode_body(cursor: &mut SbeCursor<'_>) -> Result<Self, SbeDecodeError> {
        let rate = decode_decimal(cursor)?;
        let interval_raw = cursor.read_u16_le()?;
        let next_funding_raw = cursor.read_u64_le()?;
        let ts_event = decode_unix_nanos(cursor)?;
        let ts_init = decode_unix_nanos(cursor)?;
        let instrument_id = decode_instrument_id(cursor)?;

        Ok(Self {
            instrument_id,
            rate,
            interval: (interval_raw != u16::MAX).then_some(interval_raw),
            next_funding_ns: (next_funding_raw != u64::MAX).then_some(next_funding_raw.into()),
            ts_event,
            ts_init,
        })
    }

    fn encoded_body_size(&self) -> usize {
        usize::from(Self::BLOCK_LENGTH) + encoded_instrument_id_size(&self.instrument_id)
    }
}

impl MarketSbeMessage for OptionGreeks {
    const TEMPLATE_ID: u16 = template_id::OPTION_GREEKS;
    const BLOCK_LENGTH: u16 = 98;

    fn encode_body(&self, writer: &mut SbeWriter<'_>) -> Result<(), SbeEncodeError> {
        writer.write_u8(self.convention as u8);
        writer.write_u8(option_greeks_optional_mask(self));
        encode_float64(writer, self.greeks.delta);
        encode_float64(writer, self.greeks.gamma);
        encode_float64(writer, self.greeks.vega);
        encode_float64(writer, self.greeks.theta);
        encode_float64(writer, self.greeks.rho);
        encode_optional_float64(writer, self.mark_iv);
        encode_optional_float64(writer, self.bid_iv);
        encode_optional_float64(writer, self.ask_iv);
        encode_optional_float64(writer, self.underlying_price);
        encode_optional_float64(writer, self.open_interest);
        encode_unix_nanos(writer, self.ts_event);
        encode_unix_nanos(writer, self.ts_init);
        encode_instrument_id(writer, &self.instrument_id)
    }

    fn decode_body(cursor: &mut SbeCursor<'_>) -> Result<Self, SbeDecodeError> {
        let convention = decode_greeks_convention(cursor)?;
        let optional_mask = cursor.read_u8()?;
        validate_option_greeks_optional_mask(optional_mask)?;
        let greeks = OptionGreekValues {
            delta: decode_float64(cursor)?,
            gamma: decode_float64(cursor)?,
            vega: decode_float64(cursor)?,
            theta: decode_float64(cursor)?,
            rho: decode_float64(cursor)?,
        };
        let mark_iv = decode_optional_float64(cursor, optional_mask, OPTION_GREEKS_HAS_MARK_IV)?;
        let bid_iv = decode_optional_float64(cursor, optional_mask, OPTION_GREEKS_HAS_BID_IV)?;
        let ask_iv = decode_optional_float64(cursor, optional_mask, OPTION_GREEKS_HAS_ASK_IV)?;
        let underlying_price =
            decode_optional_float64(cursor, optional_mask, OPTION_GREEKS_HAS_UNDERLYING_PRICE)?;
        let open_interest =
            decode_optional_float64(cursor, optional_mask, OPTION_GREEKS_HAS_OPEN_INTEREST)?;
        let ts_event = decode_unix_nanos(cursor)?;
        let ts_init = decode_unix_nanos(cursor)?;
        let instrument_id = decode_instrument_id(cursor)?;

        Ok(Self {
            instrument_id,
            convention,
            greeks,
            mark_iv,
            bid_iv,
            ask_iv,
            underlying_price,
            open_interest,
            ts_event,
            ts_init,
        })
    }

    fn encoded_body_size(&self) -> usize {
        usize::from(Self::BLOCK_LENGTH) + encoded_instrument_id_size(&self.instrument_id)
    }
}

impl MarketSbeMessage for InstrumentStatus {
    const TEMPLATE_ID: u16 = template_id::INSTRUMENT_STATUS;
    const BLOCK_LENGTH: u16 = 21;

    fn encode_body(&self, writer: &mut SbeWriter<'_>) -> Result<(), SbeEncodeError> {
        writer.write_u16_le(self.action as u16);
        writer.write_u8(encode_optional_bool(self.is_trading));
        writer.write_u8(encode_optional_bool(self.is_quoting));
        writer.write_u8(encode_optional_bool(self.is_short_sell_restricted));
        encode_unix_nanos(writer, self.ts_event);
        encode_unix_nanos(writer, self.ts_init);
        encode_instrument_id(writer, &self.instrument_id)?;
        encode_optional_ustr(writer, "InstrumentStatus.reason", self.reason)?;
        encode_optional_ustr(writer, "InstrumentStatus.trading_event", self.trading_event)
    }

    fn decode_body(cursor: &mut SbeCursor<'_>) -> Result<Self, SbeDecodeError> {
        let action = decode_market_status_action(cursor)?;
        let is_trading = decode_optional_bool(cursor, "InstrumentStatus.is_trading")?;
        let is_quoting = decode_optional_bool(cursor, "InstrumentStatus.is_quoting")?;
        let is_short_sell_restricted =
            decode_optional_bool(cursor, "InstrumentStatus.is_short_sell_restricted")?;
        let ts_event = decode_unix_nanos(cursor)?;
        let ts_init = decode_unix_nanos(cursor)?;
        let instrument_id = decode_instrument_id(cursor)?;
        let reason = decode_optional_ustr(cursor)?;
        let trading_event = decode_optional_ustr(cursor)?;

        Ok(Self {
            instrument_id,
            action,
            ts_event,
            ts_init,
            reason,
            trading_event,
            is_trading,
            is_quoting,
            is_short_sell_restricted,
        })
    }

    fn encoded_body_size(&self) -> usize {
        usize::from(Self::BLOCK_LENGTH)
            + encoded_instrument_id_size(&self.instrument_id)
            + encoded_optional_ustr_size(self.reason)
            + encoded_optional_ustr_size(self.trading_event)
    }
}

impl MarketSbeMessage for InstrumentClose {
    const TEMPLATE_ID: u16 = template_id::INSTRUMENT_CLOSE;
    const BLOCK_LENGTH: u16 = PRICE_BLOCK_LENGTH + 17;

    fn encode_body(&self, writer: &mut SbeWriter<'_>) -> Result<(), SbeEncodeError> {
        encode_price(writer, &self.close_price);
        writer.write_u8(self.close_type as u8);
        encode_unix_nanos(writer, self.ts_event);
        encode_unix_nanos(writer, self.ts_init);
        encode_instrument_id(writer, &self.instrument_id)
    }

    fn decode_body(cursor: &mut SbeCursor<'_>) -> Result<Self, SbeDecodeError> {
        let close_price = decode_price(cursor)?;
        let close_type = decode_instrument_close_type(cursor)?;
        let ts_event = decode_unix_nanos(cursor)?;
        let ts_init = decode_unix_nanos(cursor)?;
        let instrument_id = decode_instrument_id(cursor)?;

        Ok(Self {
            instrument_id,
            close_price,
            close_type,
            ts_event,
            ts_init,
        })
    }

    fn encoded_body_size(&self) -> usize {
        usize::from(Self::BLOCK_LENGTH) + encoded_instrument_id_size(&self.instrument_id)
    }
}

const OPTION_GREEKS_HAS_MARK_IV: u8 = 1 << 0;
const OPTION_GREEKS_HAS_BID_IV: u8 = 1 << 1;
const OPTION_GREEKS_HAS_ASK_IV: u8 = 1 << 2;
const OPTION_GREEKS_HAS_UNDERLYING_PRICE: u8 = 1 << 3;
const OPTION_GREEKS_HAS_OPEN_INTEREST: u8 = 1 << 4;
const OPTION_GREEKS_KNOWN_OPTIONAL_MASK: u8 = OPTION_GREEKS_HAS_MARK_IV
    | OPTION_GREEKS_HAS_BID_IV
    | OPTION_GREEKS_HAS_ASK_IV
    | OPTION_GREEKS_HAS_UNDERLYING_PRICE
    | OPTION_GREEKS_HAS_OPEN_INTEREST;

#[inline]
fn option_greeks_optional_mask(value: &OptionGreeks) -> u8 {
    let mut mask = 0;
    if value.mark_iv.is_some() {
        mask |= OPTION_GREEKS_HAS_MARK_IV;
    }

    if value.bid_iv.is_some() {
        mask |= OPTION_GREEKS_HAS_BID_IV;
    }

    if value.ask_iv.is_some() {
        mask |= OPTION_GREEKS_HAS_ASK_IV;
    }

    if value.underlying_price.is_some() {
        mask |= OPTION_GREEKS_HAS_UNDERLYING_PRICE;
    }

    if value.open_interest.is_some() {
        mask |= OPTION_GREEKS_HAS_OPEN_INTEREST;
    }
    mask
}

fn validate_option_greeks_optional_mask(mask: u8) -> Result<(), SbeDecodeError> {
    if mask & !OPTION_GREEKS_KNOWN_OPTIONAL_MASK != 0 {
        return Err(SbeDecodeError::InvalidValue {
            field: "OptionGreeks.optional_mask",
        });
    }
    Ok(())
}

#[inline]
fn encode_float64(writer: &mut SbeWriter<'_>, value: f64) {
    writer.write_u64_le(value.to_bits());
}

#[inline]
fn encode_optional_float64(writer: &mut SbeWriter<'_>, value: Option<f64>) {
    encode_float64(writer, value.unwrap_or(0.0));
}

#[inline]
fn decode_float64(cursor: &mut SbeCursor<'_>) -> Result<f64, SbeDecodeError> {
    Ok(f64::from_bits(cursor.read_u64_le()?))
}

fn decode_optional_float64(
    cursor: &mut SbeCursor<'_>,
    mask: u8,
    flag: u8,
) -> Result<Option<f64>, SbeDecodeError> {
    let value = decode_float64(cursor)?;
    Ok((mask & flag != 0).then_some(value))
}
