use std::str::FromStr;

use tokio::runtime::Runtime;

use hickory_proto::rr::{rdata::CNAME, Name, RData, Record, RecordType};
use hickory_server::{
    authority::{Authority, ZoneType},
    store::in_memory::InMemoryAuthority,
};

#[test]
fn test_cname_loop() {
    let runtime = Runtime::new().expect("failed to create Tokio Runtime");
    let mut auth = InMemoryAuthority::empty(
        Name::from_str("example.com.").unwrap(),
        ZoneType::Primary,
        false,
    );

    auth.upsert_mut(
        Record::from_rdata(
            Name::from_str("foo.example.com.").unwrap(),
            300,
            RData::CNAME(CNAME(Name::from_str("foo.example.com.").unwrap())),
        ),
        0,
    );

    auth.upsert_mut(
        Record::from_rdata(
            Name::from_str("bar.example.com.").unwrap(),
            300,
            RData::CNAME(CNAME(Name::from_str("foo.example.com.").unwrap())),
        ),
        0,
    );

    auth.upsert_mut(
        Record::from_rdata(
            Name::from_str("baz.example.com.").unwrap(),
            300,
            RData::CNAME(CNAME(Name::from_str("boz.example.com.").unwrap())),
        ),
        0,
    );

    auth.upsert_mut(
        Record::from_rdata(
            Name::from_str("boz.example.com.").unwrap(),
            300,
            RData::CNAME(CNAME(Name::from_str("biz.example.com.").unwrap())),
        ),
        0,
    );

    auth.upsert_mut(
        Record::from_rdata(
            Name::from_str("biz.example.com.").unwrap(),
            300,
            RData::CNAME(CNAME(Name::from_str("baz.example.com.").unwrap())),
        ),
        0,
    );

    let mut lookup = runtime
        .block_on(auth.lookup(
            &Name::from_str("foo.example.com.").unwrap().into(),
            RecordType::A,
            Default::default(),
        ))
        .unwrap();

    let records: Vec<&Record> = lookup.iter().collect();
    assert_eq!(records.len(), 1);
    let record = records[0];
    assert_eq!(record.name(), &Name::from_str("foo.example.com.").unwrap());
    assert_eq!(
        record.data(),
        Some(&RData::CNAME(CNAME(
            Name::from_str("foo.example.com.").unwrap()
        )))
    );

    assert!(
        lookup.take_additionals().is_none(),
        "Should be no additional records."
    );

    let mut lookup = runtime
        .block_on(auth.lookup(
            &Name::from_str("bar.example.com.").unwrap().into(),
            RecordType::A,
            Default::default(),
        ))
        .unwrap();

    let records: Vec<&Record> = lookup.iter().collect();
    assert_eq!(records.len(), 1);
    let record = records[0];
    assert_eq!(record.name(), &Name::from_str("bar.example.com.").unwrap());
    assert_eq!(
        record.data(),
        Some(&RData::CNAME(CNAME(
            Name::from_str("foo.example.com.").unwrap()
        )))
    );

    let additionals = lookup
        .take_additionals()
        .expect("Should be additional records");
    let additionals: Vec<&Record> = additionals.iter().collect();
    assert_eq!(additionals.len(), 1);
    let record = additionals[0];
    assert_eq!(record.name(), &Name::from_str("foo.example.com.").unwrap());
    assert_eq!(
        record.data(),
        Some(&RData::CNAME(CNAME(
            Name::from_str("foo.example.com.").unwrap()
        )))
    );

    let mut lookup = runtime
        .block_on(auth.lookup(
            &Name::from_str("baz.example.com.").unwrap().into(),
            RecordType::A,
            Default::default(),
        ))
        .unwrap();

    let records: Vec<&Record> = lookup.iter().collect();
    assert_eq!(records.len(), 1);
    let record = records[0];
    assert_eq!(record.name(), &Name::from_str("baz.example.com.").unwrap());
    assert_eq!(
        record.data(),
        Some(&RData::CNAME(CNAME(
            Name::from_str("boz.example.com.").unwrap()
        )))
    );

    let additionals = lookup
        .take_additionals()
        .expect("Should be additional records");
    let additionals: Vec<&Record> = additionals.iter().collect();
    assert_eq!(additionals.len(), 2);
    let record = additionals[0];
    assert_eq!(record.name(), &Name::from_str("boz.example.com.").unwrap());
    assert_eq!(
        record.data(),
        Some(&RData::CNAME(CNAME(
            Name::from_str("biz.example.com.").unwrap()
        )))
    );
    let record = additionals[1];
    assert_eq!(record.name(), &Name::from_str("biz.example.com.").unwrap());
    assert_eq!(
        record.data(),
        Some(&RData::CNAME(CNAME(
            Name::from_str("baz.example.com.").unwrap()
        )))
    );
}
