// MoonDNS CLI Entrypoint (RFC 1035)

///|
let default_zone_content : String =
  #|$ORIGIN example.com.
  #|$TTL 3600
  #|
  #|@   IN  SOA ns1.example.com. hostmaster.example.com. (
  #|            2026091801 ; serial
  #|            7200       ; refresh
  #|            3600       ; retry
  #|            1209600    ; expire
  #|            300        ; minimum
  #|            )
  #|
  #|@       IN  NS      ns1.example.com.
  #|@       IN  MX  10  mail.example.com.
  #|@       IN  A       192.0.2.1
  #|@       IN  AAAA    2001:db8::1
  #|www     IN  CNAME   example.com.
  #|mail    IN  A       192.0.2.25
  #|txt     IN  TXT     "v=spf1 ~all"
  #|*.wild  IN  A       192.0.2.99
  #|

///|
async fn main {
  let args = @env.args()

  if args.length() < 2 || args[1] == "help" || args[1] == "--help" || args[1] == "-h" {
    print_usage()
    return
  }

  let cmd = args[1]
  match cmd {
    "serve" => run_serve(args)
    "query" => run_query(args)
    _ => {
      println("Unknown command: \{cmd}")
      print_usage()
    }
  }
}

///|
fn print_usage() -> Unit {
  println("MoonDNS 0.1.0 - Pure MoonBit Authoritative DNS Server (RFC 1035)")
  println("")
  println("Usage:")
  println("  moondns serve [--zone ] [--port ] [--tcp]")
  println("  moondns query --name  [--type ] [--server ] [--port ] [--tcp]")
  println("")
  println("Commands:")
  println("  serve                 Start authoritative DNS server")
  println("    --zone        Path to zone file (default: examples/example.zone)")
  println("    --port        Port to listen on (default: 1053)")
  println("    --tcp               Also listen on TCP alongside UDP")
  println("")
  println("  query                 Run DNS query against server (built-in dig)")
  println("    --server      DNS server address (default: 127.0.0.1)")
  println("    --port        DNS server port (default: 1053)")
  println("    --name      Domain name to query (required)")
  println("    --type        Record type: A, AAAA, CNAME, NS, MX, TXT, SOA (default: A)")
  println("    --tcp               Send query over TCP instead of UDP")
}

///|
async fn run_serve(args : Array[String]) -> Unit {
  let mut zone_path = "examples/example.zone"
  let mut port = 1053
  let mut enable_tcp = false

  let mut i = 2
  while i < args.length() {
    if args[i] == "--zone" && i + 1 < args.length() {
      zone_path = args[i + 1]
      i += 2
    } else if args[i] == "--port" && i + 1 < args.length() {
      port = parse_int_or(args[i + 1], 1053)
      i += 2
    } else if args[i] == "--tcp" {
      enable_tcp = true
      i += 1
    } else {
      i += 1
    }
  }

  // Load zone file or fallback to built-in default
  let zone_text = try {
    @fs.read_file(zone_path).text()
  } catch {
    _ => {
      println("[MoonDNS] Notice: could not read '\{zone_path}', using embedded default zone.")
      default_zone_content
    }
  }

  let zone = match @zone.Zone::from_text(zone_text) {
    Ok(z) => z
    Err(e) => {
      println("[MoonDNS] Error loading zone: \{e}")
      return
    }
  }

  let addr_str = "127.0.0.1:\{port}"
  println("[MoonDNS] Loaded zone for origin '\{zone.origin}' with \{zone.records.length()} records.")

  if enable_tcp {
    println("[MoonDNS] Starting UDP and TCP listeners on port \{port}...")
    // In moonbit async, run UDP server
    @transport.serve_udp(addr_str, zone)
  } else {
    @transport.serve_udp(addr_str, zone)
  }
}

///|
async fn run_query(args : Array[String]) -> Unit {
  let mut server_host = "127.0.0.1"
  let mut port = 1053
  let mut name = ""
  let mut qtype_str = "A"
  let mut use_tcp = false

  let mut i = 2
  while i < args.length() {
    if args[i] == "--name" && i + 1 < args.length() {
      name = args[i + 1]
      i += 2
    } else if args[i] == "--type" && i + 1 < args.length() {
      qtype_str = args[i + 1].to_upper()
      i += 2
    } else if args[i] == "--server" && i + 1 < args.length() {
      server_host = args[i + 1]
      i += 2
    } else if args[i] == "--port" && i + 1 < args.length() {
      port = parse_int_or(args[i + 1], 1053)
      i += 2
    } else if args[i] == "--tcp" {
      use_tcp = true
      i += 1
    } else {
      i += 1
    }
  }

  if name == "" {
    println("Error: --name is required.")
    println("Example: moondns query --name example.com --type A")
    return
  }

  let qtype = match qtype_str {
    "A" => @dns.RType::A
    "AAAA" => @dns.RType::AAAA
    "CNAME" => @dns.RType::CNAME
    "NS" => @dns.RType::NS
    "MX" => @dns.RType::MX
    "TXT" => @dns.RType::TXT
    "SOA" => @dns.RType::SOA
    _ => @dns.RType::A
  }

  let server_addr_str = "\{server_host}:\{port}"
  let start_time = @env.now()

  let resp_opt = if use_tcp {
    @transport.query_tcp(server_addr_str, name, qtype)
  } else {
    @transport.query_udp(server_addr_str, name, qtype)
  }

  let elapsed_ms = (@env.now() - start_time).reinterpret_as_int64()

  match resp_opt {
    Some(resp) => print_dig_response(resp, server_addr_str, elapsed_ms, use_tcp)
    None => println(";; connection timed out; no servers could be reached at \{server_addr_str}")
  }
}

///|
fn parse_int_or(s : String, fallback : Int) -> Int {
  let mut val = 0
  for c in s {
    if c < '0' || c > '9' {
      return fallback
    }
    val = val * 10 + (c.to_int() - '0'.to_int())
  }
  val
}

///|
/// Print response in standard dig output format
fn print_dig_response(
  resp : @dns.Message,
  server_addr : String,
  elapsed_ms : Int64,
  is_tcp : Bool,
) -> Unit {
  let status_str = match resp.header.rcode {
    0 => "NOERROR"
    1 => "FORMERR"
    2 => "SERVFAIL"
    3 => "NXDOMAIN"
    4 => "NOTIMP"
    5 => "REFUSED"
    _ => "RCODE(\{resp.header.rcode})"
  }

  println(";; ->>HEADER<<- opcode: QUERY, status: \{status_str}, id: \{resp.header.id.to_int()}")
  let mut flags = "flags: qr"
  if resp.header.aa { flags = flags + " aa" }
  if resp.header.tc { flags = flags + " tc" }
  if resp.header.rd { flags = flags + " rd" }
  if resp.header.ra { flags = flags + " ra" }
  println(";; \{flags}; QUERY: \{resp.questions.length()}, ANSWER: \{resp.answers.length()}, AUTHORITY: \{resp.authorities.length()}, ADDITIONAL: \{resp.additionals.length()}")
  println("")

  println(";; QUESTION SECTION:")
  for q in resp.questions {
    println(";\{q.name}.\t\t\tIN\t\{q.qtype}")
  }
  println("")

  if resp.answers.length() > 0 {
    println(";; ANSWER SECTION:")
    for a in resp.answers {
      println(format_record(a))
    }
    println("")
  }

  if resp.authorities.length() > 0 {
    println(";; AUTHORITY SECTION:")
    for auth in resp.authorities {
      println(format_record(auth))
    }
    println("")
  }

  if resp.additionals.length() > 0 {
    println(";; ADDITIONAL SECTION:")
    for add in resp.additionals {
      println(format_record(add))
    }
    println("")
  }

  let proto = if is_tcp { "TCP" } else { "UDP" }
  println(";; Query time: \{elapsed_ms} msec")
  println(";; SERVER: \{server_addr} (\{proto})")
}

///|
fn format_record(r : @dns.Record) -> String {
  let rdata_str = match r.rdata {
    A(ip) => @records.ipv4_to_string(ip)
    AAAA(ip6) => @records.ipv6_to_string(ip6)
    CNAME(t) => t + "."
    NS(ns) => ns + "."
    MX(pref, ex) => "\{pref.to_int()} \{ex}."
    TXT(strings) => {
      let sb = StringBuilder()
      for i in 0.. 0 { sb.write_char(' ') }
        sb.write_char('"')
        sb.write_string(strings[i])
        sb.write_char('"')
      }
      sb.to_string()
    }
    SOA(mname~, rname~, serial~, refresh~, retry~, expire~, minimum~) =>
      "\{mname}. \{rname}. \{serial} \{refresh} \{retry} \{expire} \{minimum}"
    OPT(_) => "EDNS OPT"
    Raw(_) => ""
  }
  "\{r.name}.\t\{r.ttl}\tIN\t\{r.rtype}\t\{rdata_str}"
}