import { createDb, domain } from "@minmon/db";
import { eq } from "drizzle-orm";
import { resolve4 } from "node:dns/promises";
import { Effect } from "effect";

export type DomainCheckStatus = "MATCH" | "MISMATCH" | "UNRESOLVED";

export type DomainCheckResult = {
  status: DomainCheckStatus;
  resolvedIps: string[];
  checkedAt: Date;
  message: string | null;
};

type DomainCheckTarget = {
  name: string;
  expectedServerIp: string;
};

export function resolveIpv4ARecords(hostname: string) {
  return Effect.tryPromise({
    try: async () => [...new Set(await resolve4(hostname))],
    catch: (error: unknown) =>
      error instanceof Error ? error : new Error("Unable to resolve IPv4 A records"),
  });
}

export function checkDomainDns(target: DomainCheckTarget) {
  return Effect.match(resolveIpv4ARecords(target.name), {
    onSuccess: (resolvedIps: string[]): DomainCheckResult => ({
      status: resolvedIps.includes(target.expectedServerIp) ? "MATCH" : "MISMATCH",
      resolvedIps,
      checkedAt: new Date(),
      message: null,
    }),
    onFailure: (error: Error): DomainCheckResult => ({
      status: "UNRESOLVED",
      resolvedIps: [],
      checkedAt: new Date(),
      message: error.message,
    }),
  });
}

export async function persistDomainCheck(
  database: ReturnType<typeof createDb>,
  input: {
    domainId: string;
    result: DomainCheckResult;
  },
) {
  const [updatedDomain] = await database
    .update(domain)
    .set({
      resolutionStatus: input.result.status,
      lastResolvedIps: input.result.resolvedIps,
      lastResolvedIp: input.result.resolvedIps[0] ?? null,
      lastCheckedAt: input.result.checkedAt,
      lastCheckMessage: input.result.message,
    })
    .where(eq(domain.id, input.domainId))
    .returning();

  return updatedDomain ?? null;
}

export async function runAndPersistDomainCheck(
  database: ReturnType<typeof createDb>,
  input: {
    domainId: string;
    name: string;
    expectedServerIp: string;
  },
) {
  const result = await Effect.runPromise(
    checkDomainDns({
      name: input.name,
      expectedServerIp: input.expectedServerIp,
    }),
  );

  const updatedDomain = await persistDomainCheck(database, {
    domainId: input.domainId,
    result,
  });

  return {
    result,
    domain: updatedDomain,
  };
}
