use std::hint::black_box;

use easyfix_messages::{
    fields::{
        AccountType, FixString, MsgType, OrdType, OrderAttributeType, OrderCapacity, PartyIdSource,
        PartyRole, PartyRoleQualifier, Price, SecurityIdSource, SeqNum, Side, TimeInForce,
        ToFixString, UtcTimestamp,
    },
    groups::{OrderAttributeGrp, Parties},
    messages::{FixtMessage, Header, Message, NewOrderSingle, BEGIN_STRING},
};

struct FixMsgGen {
    msg_seq_num: SeqNum,
}

impl FixMsgGen {
    fn new() -> FixMsgGen {
        FixMsgGen { msg_seq_num: 0 }
    }

    fn next_msg_seq_num(&mut self) -> SeqNum {
        self.msg_seq_num += 1;
        self.msg_seq_num
    }

    fn header(&mut self, msg_type: MsgType) -> Header {
        Header {
            begin_string: BEGIN_STRING.to_owned(),
            body_length: 0,
            msg_type,
            sender_comp_id: 800.to_fix_string(),
            target_comp_id: FixString::from_ascii(b"n8_fix_tp".to_vec()).unwrap(),
            msg_seq_num: self.next_msg_seq_num(),
            sender_sub_id: None,
            target_sub_id: None,
            sending_time: UtcTimestamp::now(),
            ..Default::default()
        }
    }

    fn new_order_single(&mut self, cl_ord_id: FixString) -> Box<FixtMessage> {
        let order_single = NewOrderSingle {
            cl_ord_id,
            parties: vec![
                Parties {
                    party_id: 4294967212u64.to_fix_string(),
                    party_id_source: PartyIdSource::ShortCodeIdentifier,
                    party_role: PartyRole::ClientId,
                    party_role_qualifier: Some(PartyRoleQualifier::NaturalPerson),
                },
                Parties {
                    party_id: 3294967200u64.to_fix_string(),
                    party_id_source: PartyIdSource::ShortCodeIdentifier,
                    party_role: PartyRole::ExecutingTrader,
                    party_role_qualifier: Some(PartyRoleQualifier::Algorithm),
                },
                Parties {
                    party_id: 5483847u64.to_fix_string(),
                    party_id_source: PartyIdSource::ShortCodeIdentifier,
                    party_role: PartyRole::InvestmentDecisionMaker,
                    party_role_qualifier: Some(PartyRoleQualifier::NaturalPerson),
                },
            ],
            account: Some(FixString::from_ascii(b"ACCOUNT003".to_vec()).unwrap()),
            account_type: Some(AccountType::AccountIsCarriedOnCustomerSideOfTheBooks),
            exec_inst: None,
            self_match_prevention_id: None,
            display_qty: None,
            initial_display_qty: None,
            security_id: 9000.to_fix_string(),
            security_id_source: SecurityIdSource::ExchangeSymbol,
            side: Side::Sell,
            transact_time: UtcTimestamp::now(),
            order_qty: 96.into(),
            ord_type: OrdType::Limit,
            price: Some(Price::from_str_exact("100.1").expect("invalid price")),
            trigger_price: None,
            time_in_force: TimeInForce::Day,
            expire_date: None,
            expire_time: None,
            order_capacity: OrderCapacity::Agency,
            order_restrictions: None,
            order_attribute_grp: vec![OrderAttributeGrp {
                order_attribute_type: OrderAttributeType::LiquidityProvisionActivityOrder,
                order_attribute_value: b'N'.to_fix_string(),
            }],
            text: None,
            order_origination: None,
            fee_structure_id: None,
        };

        Box::new(FixtMessage {
            header: Box::new(self.header(MsgType::NewOrderSingle)),
            body: Box::new(Message::NewOrderSingle(order_single)),
            trailer: Default::default(),
        })
    }
}

#[inline(never)]
fn benchmark_serialize() {
    let mut msg_gen = FixMsgGen::new();
    let msg = msg_gen.new_order_single(123456.to_fix_string());

    for _ in 0..1_000_000 {
        let _ = black_box(&msg).serialize();
    }
}

#[inline(never)]
fn benchmark_from_bytes() {
    let serialized = "8=FIXT.1.1|9=0302|35=D|49=800|56=n8_fix_tp|34=1|52=20240919-12:42:29.030820000|11=123456|453=3|448=4294967212|447=P|452=3|2376=24|448=3294967200|447=P|452=12|2376=22|448=5483847|447=P|452=122|2376=24|1=ACCOUNT003|581=1|48=9000|22=8|54=2|60=20240919-12:42:29.030820000|38=96|40=2|44=100.1|59=0|528=A|2593=1|2594=2|2595=N|10=050|".replace('|', "\x01").into_bytes();

    for _ in 0..1_000_000 {
        let _ = FixtMessage::from_bytes(black_box(&serialized));
    }
}

fn main() {
    benchmark_from_bytes();

    benchmark_serialize();
}
