use std::hint::black_box;

use criterion::{criterion_group, criterion_main, Criterion};
use easyfix_macros::fix_str;
use easyfix_messages::{
    fields::{
        AccountType, FixStr, FixString, MsgType, OrdType, OrderAttributeType, OrderCapacity,
        PartyIdSource, PartyRole, PartyRoleQualifier, Price, SecurityIdSource, SeqNum, Side,
        TimeInForce, 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: fix_str!("800").to_owned(),
            target_comp_id: fix_str!("n8_fix_tp").to_owned(),
            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: fix_str!("4294967212").to_owned(),
                    party_id_source: PartyIdSource::ShortCodeIdentifier,
                    party_role: PartyRole::ClientId,
                    party_role_qualifier: Some(PartyRoleQualifier::NaturalPerson),
                },
                Parties {
                    party_id: fix_str!("3294967200").to_owned(),
                    party_id_source: PartyIdSource::ShortCodeIdentifier,
                    party_role: PartyRole::ExecutingTrader,
                    party_role_qualifier: Some(PartyRoleQualifier::Algorithm),
                },
                Parties {
                    party_id: fix_str!("5483847").to_owned(),
                    party_id_source: PartyIdSource::ShortCodeIdentifier,
                    party_role: PartyRole::InvestmentDecisionMaker,
                    party_role_qualifier: Some(PartyRoleQualifier::NaturalPerson),
                },
            ],
            account: Some(fix_str!("ACCOUNT003").to_owned()),
            account_type: Some(AccountType::AccountIsCarriedOnCustomerSideOfTheBooks),
            exec_inst: None,
            self_match_prevention_id: None,
            display_qty: None,
            initial_display_qty: None,
            security_id: fix_str!("9000").to_owned(),
            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: fix_str!("N").to_owned(),
            }],
            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(),
        })
    }
}

fn benchmark_serialize(c: &mut Criterion) {
    let mut msg_gen = FixMsgGen::new();
    let msg = msg_gen.new_order_single(fix_str!("123456").to_owned());

    c.bench_function("serialize", |b| {
        b.iter(|| {
            let _ = black_box(&msg).serialize();
        });
    });
}

fn benchmark_from_bytes(c: &mut Criterion) {
    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();

    c.bench_function("from_bytes", |b| {
        b.iter(|| {
            let _ = FixtMessage::from_bytes(black_box(&serialized));
        });
    });
}

criterion_group!(benches, benchmark_serialize, benchmark_from_bytes);
criterion_main!(benches);
