/*use cloudflare_old::{Instance, CloudflareDnsType};*/
use reqwest::blocking::get;
use std::{thread::{sleep}};
use std::error::Error;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
use chrono::{Utc, Duration};
use dotenv::dotenv;
use log::{info, warn, error, LevelFilter};
use reqwest::StatusCode;
use systemd_journal_logger::{connected_to_journal, JournalLog};
use crate::cloudflare::{CloudflareZone, DnsRecordType};

mod config;
mod cloudflare;

struct Addresses {
    ipv4_uri: String,
    ipv6_uri: String,
    ipv4: Ipv4Addr,
    ipv6: Ipv6Addr,
}

impl Addresses {
    fn new() -> Result<Self, Box<dyn Error>> {
        let mut ret = Self {
            ipv4_uri: "https://am.i.mullvad.net/ip".to_owned(),
            ipv6_uri: "https://ipv6.am.i.mullvad.net/ip".to_owned(),
            ipv4: Ipv4Addr::new(0, 0, 0, 0),
            ipv6: Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)
        };

        match ret.get_v4() {
            Ok(ip) => ret.ipv4 = ip,
            Err(_) => {
                let err_msg = format!("Unable to fetch IPv4 from '{}' during init. Aborting!", ret.ipv4_uri);
                match connected_to_journal() {
                    true => error!("[ERROR] {err_msg}"),
                    false => eprintln!("[ERROR] {err_msg}"),
                }
                panic!("{}", err_msg);
            }
        }

        match ret.get_v6() {
            Ok(ip) => ret.ipv6 = ip,
            Err(_) => {
                let err_msg = format!("Unable to fetch IPv6 from '{}' during init. Aborting!", ret.ipv6_uri);
                match connected_to_journal() {
                    true => error!("[ERROR] {err_msg}"),
                    false => eprintln!("[ERROR] {err_msg}"),
                }
                panic!("{}", err_msg);
            }
        }

        Ok(ret)
    }

    fn update(&mut self) {
        match self.get_v4() {
            Ok(ip) => {
                if ip != self.ipv4 {
                    let info_msg = format!("IPv4 changed from '{}' to '{}'", self.ipv4, ip);
                    match connected_to_journal() {
                        true => info!("[INFO] {info_msg}"),
                        false => println!("[INFO] {info_msg}"),
                    }
                    self.ipv4 = ip;
                }
            }
            Err(e) => {
                let warn_msg = format!("Unable to fetch IPv4 from '{}'. Error: {}", self.ipv4_uri, e);
                match connected_to_journal() {
                    true => warn!("[WARN] {warn_msg}"),
                    false => println!("[WARN] {warn_msg}"),
                }
            }
        }

        match self.get_v6() {
            Ok(ip) => {
                if ip != self.ipv6 {
                    let info_msg = format!("IPv6 changed from '{}' to '{}'", self.ipv6, ip);
                    match connected_to_journal() {
                        true => info!("[INFO] {info_msg}"),
                        false => println!("[INFO] {info_msg}"),
                    }
                    self.ipv6 = ip;
                }
            }
            Err(e) => {
                let warn_msg = format!("Unable to fetch IPv6 from '{}'. Error: {}", self.ipv6_uri, e);
                match connected_to_journal() {
                    true => warn!("[WARN] {warn_msg}"),
                    false => println!("[WARN] {warn_msg}"),
                }
            }
        }
    }

    fn get_v4(&self) -> Result<Ipv4Addr, reqwest::Error> {
        match get(&self.ipv4_uri) {
            Ok(res) => {
                match res.status() {
                    StatusCode::OK => {
                        let ip_string = res.text().expect("Returned data should always contain text").trim_end().to_owned();
                        Ok(Ipv4Addr::from_str(ip_string.as_str()).expect("Returned IP should always be parseable"))
                    },
                    _ => {
                        let warn_msg = format!("Unexpected HTTP status {}", res.status());
                        match connected_to_journal() {
                            true => warn!("[WARN] {warn_msg}"),
                            false => println!("[WARN] {warn_msg}"),
                        }
                        Ok(Ipv4Addr::new(0, 0, 0, 0))
                    }
                }
            }
            Err(e) => Err(e),
        }

    }

    fn get_v6(&self) -> Result<Ipv6Addr, reqwest::Error> {
        match get(&self.ipv6_uri) {
            Ok(res) => {
                match res.status() {
                    StatusCode::OK => {
                        let ip_string = res.text().expect("Returned data should always contain text").trim_end().to_owned();
                        Ok(Ipv6Addr::from_str(ip_string.as_str()).expect("Returned IP should always be parseable"))
                    },
                    _ => {
                        let warn_msg = format!("Unexpected HTTP status {}", res.status());
                        match connected_to_journal() {
                            true => warn!("[WARN] {warn_msg}"),
                            false => println!("[WARN] {warn_msg}"),
                        }
                        Ok(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0))
                    }
                }
            },
            Err(e) => Err(e),
        }
    }
}

fn main() {
    dotenv().ok();
    JournalLog::new().expect("Systemd-Logger crate error").install().expect("Systemd-Logger crate error");
    log::set_max_level(LevelFilter::Info);

    let mut ifaces = config::InterfaceConfig::load().unwrap();
    let mut zone_cfgs= config::ZoneConfig::load().unwrap();

    let mut now = Utc::now() - Duration::seconds(59);
    let mut start = now;

    let mut ips = match Addresses::new() {
        Ok(ips) => ips,
        Err(e) => panic!("{}", e)
    };

    loop {
        now = Utc::now();
        if now >= start + Duration::seconds(60) {
            start = now;

            match config::InterfaceConfig::load() {
                Ok(new) => ifaces = new,
                Err(e) => {
                    let err_msg = format!("Unable to load ínterfaces.toml with error: {}", e);
                    match connected_to_journal() {
                        true => error!("[ERROR] {err_msg}"),
                        false => eprintln!("[ERROR] {err_msg}"),
                    }
                }
            }

            match config::ZoneConfig::load() {
                Ok(new) => zone_cfgs = new,
                Err(e) => {
                    let err_msg = format!("Unable to load from zones.d with error: {}", e);
                    match connected_to_journal() {
                        true => error!("[ERROR] {err_msg}"),
                        false => eprintln!("[ERROR] {err_msg}"),
                    }
                }
            }

            ips.update();
            for zone in &zone_cfgs {
                let cf_zone = match CloudflareZone::new(zone) {
                    Ok(data) => data,
                    Err(e) => {
                        let err_msg = format!("Cloudflare Token likely not set. Error: {}", e);
                        match connected_to_journal() {
                            true => error!("[ERROR] {err_msg}"),
                            false => eprintln!("[ERROR] {err_msg}"),
                        }
                        continue
                    }
                };

                let cf_entries = match cf_zone.get_entries() {
                    Ok(entries) => entries,
                    Err(_) => {
                        continue
                    }
                };

                for entry in &zone.entries {
                    let ipv6;
                    let ipv4;
                    match entry.r#type[..] {
                        [DnsRecordType::AAAA, DnsRecordType::A] => {
                            ipv6 = match ifaces.full_v6(&entry.interface, ips.ipv6) {
                                Ok(ip) => Some(ip),
                                Err(_) => {
                                    continue
                                }
                            };
                            ipv4 = Some(ips.ipv4);
                        },
                        [DnsRecordType::A, DnsRecordType::AAAA] => {
                            ipv6 = match ifaces.full_v6(&entry.interface, ips.ipv6) {
                                Ok(ip) => Some(ip),
                                Err(_) => {
                                    continue
                                }
                            };
                            ipv4 = Some(ips.ipv4);
                        },
                        [DnsRecordType::AAAA] => {
                            ipv6 = match ifaces.full_v6(&entry.interface, ips.ipv6) {
                                Ok(ip) => Some(ip),
                                Err(_) => {
                                    continue
                                }
                            };
                            ipv4 = None;
                        },
                        [DnsRecordType::A] => {
                            ipv6 = None;
                            ipv4 = Some(ips.ipv4);
                        },
                        _ => {
                            let warn_msg = "Config contains unsupported type identifier";
                            match connected_to_journal() {
                                true => warn!("[WARN] {warn_msg}"),
                                false => println!("[WARN] {warn_msg}"),
                            }
                            continue
                        }
                    }

                    for r#type in &entry.r#type {
                        let cf_entry = cf_entries.iter().find(|cf_entry| {
                            cf_entry.name == entry.name && &cf_entry.r#type == r#type
                        });

                        match cf_entry.unwrap().r#type {
                            DnsRecordType::A => {
                                let cf_ip = Ipv4Addr::from_str(cf_entry.unwrap().content.as_str()).expect("Cloudflare return should always be valid IP");
                                if Some(cf_ip) == ipv4 {
                                    continue
                                }
                            },
                            DnsRecordType::AAAA => {
                                let cf_ip = Ipv6Addr::from_str(cf_entry.unwrap().content.as_str()).expect("Cloudflare return should always be valid IP");
                                if Some(cf_ip) == ipv6 {
                                    continue
                                }
                            },
                            _ => {},
                        }

                        if let Some(cf_entry) = cf_entry {
                            if cf_zone.update(entry, r#type, &cf_entry.id, ipv6, ipv4).is_ok() {
                                let info_msg = format!("Updated {} DNS Record for entry '{}' in zone '{}'", r#type, entry.name, zone.name);
                                match connected_to_journal() {
                                    true => warn!("[INFO] {info_msg}"),
                                    false => println!("[INFO] {info_msg}"),
                                }
                            }
                        }
                        else if cf_zone.create(entry, r#type, ipv6, ipv4).is_ok() {
                            let info_msg = format!("Created {} DNS Record for entry '{}' in zone '{}'", r#type, entry.name, zone.name);
                            match connected_to_journal() {
                                true => info!("[INFO] {info_msg}"),
                                false => println!("[INFO] {info_msg}"),
                            };
                        }
                    }
                    // handle return values
                }
            }
        }
        else {
            sleep(std::time::Duration::from_millis(200));
        }
    }
}