import { serve, ConnInfo } from "https://deno.land/std/http/server.ts";
import { Status }          from "https://deno.land/std/http/http_status.ts";

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
import { readAll }         from "https://deno.land/std/streams/read_all.ts";
import   IP                from 'https://ai.rt.ht/ip/.js?2';

const getTLD = domain => domain.slice(domain.lastIndexOf('.') + 1);
const WHOIS = async (q, server, { port = 43 } = {}) => { 
  const [t, q2, { s = q => q } = {}] = await RDAP.type(q); if (t === 'asn') q = `AS${q2}`; 
  t === 'dns'
    ? server ??= (WHOIS.IANA.cache[s(q)] ??= await WHOIS(q, WHOIS.IANA.server).then(WHOIS.IANA.find).then(server => { if (!server) throw new Error(`WHOIS server not found for: ${q}`); return server; }))
    : server ??=                             await WHOIS(q, WHOIS.IANA.server).then(WHOIS.IANA.find).then(server => { if (!server) throw new Error(`WHOIS server not found for: ${q}`); return server; });
  const                                                         conn = await Deno.connect({ hostname: server, port });
  await                                                         conn.write(new TextEncoder().encode(q + "\r\n"));
  const                                  buffer = await readAll(conn);
  const  data = new TextDecoder().decode(buffer);               conn.close();
  return data;
};/**/WHOIS.IANA = { server: `whois.iana.org`, find: data => data?.match?.(/^whois:\s+([^\s]+)/m)?.[1] ?? '', cache: {} };

const RDAP = (q, servers) => { const [t, q2, O] = RDAP.type(q) ?? []; return RDAP[t]?.(q2, servers, O); };
RDAP.type  =  q           => { if (q === null || q === undefined) return q; const n = (!isNaN(Number(q))); if (n) return ['asn', q];
  switch (IP.v(q)) {
    case 4: return ['ipv4', q];
    case 6: return ['ipv6', q];
    default: 
      return q?.toUpperCase?.()?.startsWith?.('AS') && !isNaN(Number(q.slice(2)))
        ? ['asn', q.slice(2)]
        : ['dns', q         , { s: getTLD }];
  }
};
RDAP.query = async (t, q, servers, path, { s = x => x } = {}) => {
  servers ??= await RDAP.IANA[t].servers(s(q));
  for (const server of servers) {
    const url = `${server}${path}/${q}`;
    const response = await fetch(url);
    if (response.status !== 404)
      return await response.json();
  }
  return { error: `No records for ${t}: ${q}`, status: 404, t, servers, path, q };
};
RDAP.dns  = (q, servers, O) => RDAP.query('dns',  q, servers, 'domain', O);
RDAP.asn  = (q, servers, O) => RDAP.query('asn',  q, servers, 'autnum', O);
RDAP.ipv4 = (q, servers, O) => RDAP.query('ipv4', q, servers,     'ip', O);
RDAP.ipv6 = (q, servers, O) => RDAP.query('ipv6', q, servers,     'ip', O);
RDAP.IANA = {};
RDAP.IANA.dns = { cache: {},
  servers: async domain => {     const tld = getTLD(domain);
    const  srv =   RDAP.IANA.dns.cache[tld];
    return srv ?? (RDAP.IANA.dns.cache[tld] ??= await RDAP.IANA.data('dns').then(dns => dns.services[tld]).then(srv => { if (srv === undefined) throw new Error(`RDAP servers not found for TLD: ${tld}`); return srv; }));
  },
};
const            TS =       ( t, find) => ({ servers: q => RDAP.IANA.data(t).then(D => find(q, D)).then(S => { if (S === undefined) throw new Error(`RDAP servers not found for: ${q}`); return S; }), });
RDAP.IANA.asn  = TS('asn',  (no, ASN ) => { for (const range in ASN .services) { const [s, e] = range.includes('-') ? range.split('-').map(Number) : [Number(range), Number(range)]; if (no >= s && no <= e) return ASN.services[range]; } });
RDAP.IANA.ipv4 = TS('ipv4', (ip, IPv4) => { for (const range in IPv4.services) if (IP.v4.in.CIDR(ip, range)) return IPv4.services[range]; });
RDAP.IANA.ipv6 = TS('ipv6', (ip, IPv6) => { for (const range in IPv6.services) if (IP.v6.in.CIDR(ip, range)) return IPv6.services[range]; });
RDAP.IANA.data = async t => {
  const iana = RDAP.IANA.data.cache[t] ??= await fetch(RDAP.IANA.data.URL[t]).then(f => f.json());
  const data = {
    source: { url: RDAP.IANA.data.URL[t], transformation: { company: 'Elefunc.com', department: 'RTEdge.net', construct: [ 'services' ], add: [ 'servers' ] } },
    description: iana.description,
    publication: iana.publication, services: {}, servers: {},
    version:     iana.version,
  };
  for (const service of iana.services) {
    const           K  =     service [0];
    const           V =      service [1];
    for (const k of K)  data.services[k] = V;
    for (const v of V) (data.servers [v] ??= []).push(...K);
  }
  return data;
};
RDAP.IANA.data.cache = {};
RDAP.IANA.data.URL = {
  dns:  'https://data.iana.org/rdap/dns.json',
  asn:  'https://data.iana.org/rdap/asn.json',
  ipv4: 'https://data.iana.org/rdap/ipv4.json',
  ipv6: 'https://data.iana.org/rdap/ipv6.json',
};

const TLD = (q = '') => fetch('https://data.iana.org/TLD/tlds-alpha-by-domain.txt').then(f => f.text()).then(x => x.split('\n').filter(l => !l?.startsWith?.('#')).filter(l => l.trim() !== '').map(l => l.toLowerCase())).then(TLD => q === '' ? TLD : TLD.includes(q.toLowerCase()));

//await WHOIS('efn.kr')
//await RDAP(['deno.dev','efn.kr'][0]);
//await RDAP();
//await TLD();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

const         CT = `content-type`;
const TP = { [CT]: `text/plain;charset=utf-8` };
const TH = { [CT]: `text/html;charset=utf-8` };
const TJ = { [CT]: `text/javascript;charset=utf-8` };
const AJ = { [CT]: `application/json;charset=utf-8` };
const         ACXH = `access-control-expose-headers`;
const         ACAO = `access-control-allow-origin`;
const XH = { [ACXH]: '_, ip, ip-x, dur, server, server-timing' };
const AO = { [ACAO]: '*' };
const NS = { 'cache-control': 'no-store' };
const HSTS = { "strict-transport-security": "max-age=31536000; includeSubDomains; preload" };

const JP = (...v) => { try { return JSON.parse    (...v); } catch (e) { } };
const JS =                          JSON.stringify;

const resolve = async (t, q, ...O) => {
  try       { return await (t === 'AS' ? ip.info(`AS${q.replace(/^ASN?/i, '')}`) : t === 'IP' ? q : Deno.resolveDns(q, t, ...O)); } 
  catch (e) { return { error: e.message }; }
};
const rextend = async (t, q, ...O) => {
  const o = await resolve(t, q, ...O);
  return ip.type.has(t) && o.map ? (await Promise.all(o.map(ip.info))).reduce((x, { ip, ...O }) => (x[ip] = O, x), {}) : t === 'IP' ? ip.info(o) : o;
};
const registry      = (t, q, ...O) => registry[t](q, ...O).catch(e => ({ error: e.message }));
/**/  registry.RDAP  =   (q, ...O) =>        RDAP(q);
/**/  registry.WHOIS =   (q, ...O) =>       WHOIS(q);

const tld = (t, q, ...O) => TLD(q);

const F = { // [ short, extended ] output
  DENO:  [  resolve, rextend  ],
  RDAP:  [ registry, registry ],
  WHOIS: [ registry, registry ],
  TLD:   [      tld, tld      ]
};

const ip = { type: new Set([ 'A', 'AAAA', 'IP' ]) };
/**/  ip.info      = q => undefined;
/**/  ip.info.user = q => fetch(`https://ipinfo.io/${q}?${new URLSearchParams({ token: Deno.env.get('IPINFO_TOKEN') })}`).then(f => f.json())                                .catch(e => ({ ip: q, error: e.message }));
/**/  ip.info.curl = q => fetch(`https://ipinfo.io/${q}`,                   { headers: { "user-agent": "curl/7.81.0" } }).then(f => f.json()).then(o => (delete o.readme, o)).catch(e => ({ ip: q, error: e.message }));
/**/  ip.info.user.user = ip.info.user; ip.info.curl.user = ip.info.user;
/**/  ip.info.user.curl = ip.info.curl; ip.info.curl.curl = ip.info.curl;

const cartesian = (...a) => a.reduce((a, b) => a.flatMap(d => b.map(e => [d, e].flat())));
Object.prototype.sort = function({ K = true, V = false } = {}) { const C = 0; if (C) console.warn('Object.sort()', 0, { K, V }, this);
  if (!K && !V) return structuredClone(this);
  if (       V) throw new Error(`Object.sort(${JSON.stringify({ K, V })}) not implemented`); const i = structuredClone(this);
  for (const k of Object.keys(i).sort()) { delete this[k]; this[k] = i[k]; }; if (C) console.warn('Object.sort()', 1, { K, V }, this); return this;
};
const sort = x => { try { x?.sort?.(); } catch (e) {} return x; }; // MX records cannot be sorted [{},{}]

const ZQT = new Set([ 'TLD' ]);

const                                     A = new Set([ /* allowed source IPs */ ]);
serve(async (r: Request, c: ConnInfo) => { console.warn('----------------------------------------------'); console.warn(r.url, c); const ta = performance.now();
//const a = c?.remoteAddr?.hostname; if (!A.has(a)) return new Response('403 Forbidden', { status: Status.Forbidden, headers: { ...TP, ...NS, ...AO, ...HSTS } });
  ip.info = ip.info[Deno.env.get('IPINFO_TOKEN') ? 'user' : 'curl'];
  const u = new URL(r.url);
  const p = u.pathname;
  switch (p) { case '/ipinfo.io': return fetch(`https://ipinfo.io/1.1.1.1?${new URLSearchParams({ token: Deno.env.get('IPINFO_TOKEN') })}`); }
  const I = c.remoteAddr.hostname;
  const O = c. localAddr.hostname;
  const C = { IP: JS({ i: I, o: O }), "IP-X": "https://ipinfo.io", ...XH };
  const q = u.searchParams; //   _               _____________________
  const _ = JP(q.get('_')); // { _ = nameServer: { ipAddr, port = 53 } } https://deno.land/api@latest?s=Deno.ResolveDnsOptions
  const s =    q.get('s') !== null; // _={"nameServer":{"ipAddr":"1x.rtedge.net"}}
  const i =    q.get('i') ===   ''; [ '_', 's', 'i' ].forEach(x => q.delete(x));
  const f = s ? resolve : rextend;
  const                                                           X = { I, O, i: I, o: O, "": I };
  const m = (t, q) => ZQT.has(t.toUpperCase()) && q === '' ? q : (X[q] ?? q); // ←                                            ↓
  const Q = [...q.entries()].flatMap(([ T, Q ]) => cartesian(T.split(','), Q.split(',')).map(([ t, q ]) => [ t.toUpperCase(), m(t, q) ])).map(([ t, q ]) => [ t === 'X' ? 'PTR' : t, t === 'X' ? IP.ptr(q) : q ]); console.warn(Q, { i, _ });
  if   (Q.length === 0) return Response.redirect('https://apple.com', 308); // obfuscation
  if   (i)      return new Response(JS(Q, '', '  '), { headers: { _: JS(_), ...C, ...AJ, ...NS, ...AO, ...HSTS } });
  try {
    const R = await Promise.all(
      Q.map(async ([ t, q ]) => {
        const                       T = F[t] ?? F.DENO;
        const                   f = T    [s ? 0 : 1];
        return [ t, [ q, await (f ?? resolve)(t, q, _ ?? {}).then(o => ZQT.has(t) && q === '' ? o : sort(o)) ] ];
      })
    );
    const O = R.reduce((x, [ t, [ q, r ]]) => (x[t] !== undefined ? (x[t][q] = r) : (x[t] = { [q]: r }), x), {}); 
    C.dur = performance.now() - ta; 
    C['server-timing'] = `total;dur=${C.dur}`;
    /**/        return new Response(JS(O, '', '  '), { headers: { _: JS(_), ...C, ...AJ, ...NS, ...AO, ...HSTS } });
  } catch (e) { return new Response(   e.stack,      { headers: { _: JS(_), ...C, ...TP, ...NS, ...AO, ...HSTS } }); 
  }

});