import { Type } from "@sinclair/typebox";

import type { CheckerSchemas } from "../types";

import {
  createAuthoringContentExpectationsSchema,
  createAuthoringFieldSchema,
  createAuthoringValueExpectationSchema,
  createNormalizedContentExpectationsSchema,
  createNormalizedValueExpectationSchema,
  sizeSchema,
} from "../../schema/fragments";

const RECORD_TYPES = ["A", "AAAA", "CAA", "CNAME", "MX", "NS", "PTR", "SOA", "SRV", "TXT"] as const;

export const dnsCheckerSchemas: CheckerSchemas = {
  authoring: {
    config: createDnsConfigSchema("authoring"),
    expect: createDnsExpectSchema("authoring"),
  },
  normalized: {
    config: createDnsConfigSchema("normalized"),
    expect: createDnsExpectSchema("normalized"),
  },
};

function createDnsConfigSchema(kind: "authoring" | "normalized") {
  const recordType = createRecordTypeSchema();
  const family = Type.Union([Type.Literal("any"), Type.Literal("ipv4"), Type.Literal("ipv6")]);

  const systemFields = {
    family: Type.Optional(kind === "authoring" ? createAuthoringFieldSchema(family) : family),
    name:
      kind === "authoring" ? createAuthoringFieldSchema(Type.String({ minLength: 1 })) : Type.String({ minLength: 1 }),
    resolver: Type.Literal("system"),
  };

  const serverFields = {
    maxResponseBytes: Type.Optional(sizeSchema),
    name:
      kind === "authoring" ? createAuthoringFieldSchema(Type.String({ minLength: 1 })) : Type.String({ minLength: 1 }),
    port: Type.Optional(
      kind === "authoring"
        ? createAuthoringFieldSchema(Type.Integer({ maximum: 65535, minimum: 1 }))
        : Type.Integer({ maximum: 65535, minimum: 1 }),
    ),
    protocol: Type.Optional(
      kind === "authoring"
        ? createAuthoringFieldSchema(Type.Union([Type.Literal("udp"), Type.Literal("tcp")]))
        : Type.Union([Type.Literal("udp"), Type.Literal("tcp")]),
    ),
    recordType: Type.Optional(kind === "authoring" ? createAuthoringFieldSchema(recordType) : recordType),
    recursionDesired: Type.Optional(kind === "authoring" ? createAuthoringFieldSchema(Type.Boolean()) : Type.Boolean()),
    resolver: Type.Literal("server"),
    server:
      kind === "authoring" ? createAuthoringFieldSchema(Type.String({ minLength: 1 })) : Type.String({ minLength: 1 }),
    tcpFallback: Type.Optional(kind === "authoring" ? createAuthoringFieldSchema(Type.Boolean()) : Type.Boolean()),
  };

  return Type.Union(
    [
      Type.Object(systemFields, { additionalProperties: false }),
      Type.Object(serverFields, { additionalProperties: false }),
    ],
    {},
  );
}

function createDnsExpectSchema(kind: "authoring" | "normalized") {
  const valueSchema =
    kind === "authoring" ? createAuthoringValueExpectationSchema() : createNormalizedValueExpectationSchema();
  const contentSchema =
    kind === "authoring" ? createAuthoringContentExpectationsSchema() : createNormalizedContentExpectationsSchema();

  const expectFields = {
    answerCount: Type.Optional(valueSchema),
    authenticatedData: Type.Optional(Type.Boolean()),
    authoritative: Type.Optional(Type.Boolean()),
    durationMs: Type.Optional(valueSchema),
    rcode: Type.Optional(Type.Array(Type.String())),
    recursionAvailable: Type.Optional(Type.Boolean()),
    responded: Type.Optional(Type.Boolean()),
    result: Type.Optional(contentSchema),
    truncated: Type.Optional(Type.Boolean()),
    ttlMax: Type.Optional(valueSchema),
    ttlMin: Type.Optional(valueSchema),
    valueCount: Type.Optional(valueSchema),
    values: Type.Optional(createDnsValuesExpectationSchema()),
  };

  return Type.Object(expectFields, { additionalProperties: false });
}

function createDnsValuesExpectationSchema() {
  return Type.Object(
    {
      exact: Type.Optional(Type.Array(Type.String())),
      exclude: Type.Optional(Type.Array(Type.String())),
      include: Type.Optional(Type.Array(Type.String())),
    },
    { additionalProperties: false },
  );
}

function createRecordTypeSchema() {
  return Type.Union(RECORD_TYPES.map((t) => Type.Literal(t)));
}
