import { usePrisma } from "../utils/prisma";
import { createNotification } from "./notifications";

// ─── Types ───────────────────────────────────────────────────────────────────

/**
 * Typ eines Custom-Domain-Eintrags.
 *   web               — Web-Domain-Block (default, bisheriges Verhalten)
 *   mail_domain       — Sender-Domain-Block im Mail-Filter (Domain-Match)
 *   mail_display_name — Sender-Display-Name-Pattern (Substring, case-insensitive)
 *
 * Alle Types teilen den gleichen Slot-Pool pro Plan.
 */
export type CustomDomainType = "web" | "mail_domain" | "mail_display_name";

export const CUSTOM_DOMAIN_TYPES: CustomDomainType[] = [
  "web",
  "mail_domain",
  "mail_display_name",
];

// ─── Custom Domains ───────────────────────────────────────────────────────────

export async function getUserCustomDomains(userId: string) {
  const db = usePrisma();
  const rows = await db.userCustomDomain.findMany({
    where: { userId },
    orderBy: { addedAt: "desc" },
    select: {
      id: true,
      domain: true,
      status: true,
      type: true,
      postId: true,
      addedAt: true,
      submission: {
        select: { id: true, yesVotes: true, noVotes: true, status: true },
      },
    },
  });
  return rows;
}

/**
 * Counts domains that occupy a slot (active + submitted).
 * approved → slot freed (domain joined global list)
 * rejected → slot freed (user can re-submit or delete)
 *
 * @deprecated Use countActiveCustomDomainsSplit for per-type quota checks.
 */
export async function countActiveCustomDomains(userId: string) {
  const db = usePrisma();
  return db.userCustomDomain.count({
    where: { userId, status: { notIn: ["approved", "rejected"] } },
  });
}

/**
 * Returns per-bucket slot usage:
 *   web  = type === 'web'
 *   mail = type IN ('mail_domain', 'mail_display_name') — combined bucket
 *
 * Used for per-type quota enforcement (separate Free/Pro/Legend limits for web vs. mail).
 */
export async function countActiveCustomDomainsSplit(
  userId: string,
): Promise<{ web: number; mail: number }> {
  const db = usePrisma();
  const rows = await db.userCustomDomain.groupBy({
    by: ["type"],
    where: { userId, status: { notIn: ["approved", "rejected"] } },
    _count: { _all: true },
  });
  let web = 0;
  let mail = 0;
  for (const row of rows) {
    if (row.type === "web") web = row._count._all;
    else if (row.type === "mail_domain" || row.type === "mail_display_name")
      mail += row._count._all;
  }
  return { web, mail };
}

export async function addUserCustomDomain(
  userId: string,
  domain: string,
  source = "manual",
  type: CustomDomainType = "web",
) {
  const db = usePrisma();
  return db.userCustomDomain.create({
    data: { userId, domain, source, type },
    select: { id: true, domain: true, type: true },
  });
}

/**
 * Gibt alle Display-Name-Patterns eines Users zurück.
 * Wird vor jedem Mail-Scan geladen und an classifyMail() übergeben (Layer 2.6).
 *
 * DSGVO: keine PII — User-eigene Heuristik-Patterns (z.B. "EXTRASPIN").
 */
export async function getCustomMailDisplayNames(userId: string): Promise<string[]> {
  const db = usePrisma();
  const rows = await db.userCustomDomain.findMany({
    where: { userId, type: "mail_display_name" },
    select: { domain: true },
  });
  return rows.map((r) => r.domain);
}

export async function deleteUserCustomDomain(id: string, userId: string) {
  const db = usePrisma();
  // Cannot delete submitted/approved domains (protect integrity)
  const existing = await db.userCustomDomain.findFirst({
    where: { id, userId },
    select: { status: true },
  });
  if (!existing) throw Object.assign(new Error("Not found"), { code: "P2025" });
  if (existing.status === "submitted" || existing.status === "approved") {
    throw Object.assign(
      new Error(
        "Eingereichte oder genehmigte Domains können nicht gelöscht werden",
      ),
      { code: "DOMAIN_LOCKED" },
    );
  }
  return db.userCustomDomain.delete({ where: { id, userId } });
}

export async function deleteAllUserCustomDomains(userId: string) {
  const db = usePrisma();
  return db.userCustomDomain.deleteMany({ where: { userId } });
}

// ─── Domain Submissions ───────────────────────────────────────────────────────

// Net-Vote-Schwelle (yesVotes - noVotes) damit eine Submission von der
// Community-Vote-Phase in die Admin-Review-Phase wandert.
export const NET_VOTE_THRESHOLD = 10;

// Submission-Status:
//   "pending"   → Community-Voting-Phase (nur Pro)
//   "in_review" → wartet auf Admin (Legend direkt, Pro nach 10 Netto-Yes-Votes)
//   "approved"  → in globaler Blocklist
//   "rejected"  → vom Admin abgelehnt
export type SubmissionPlan = "free" | "pro" | "legend";

export async function submitDomainForReview(
  userId: string,
  customDomainId: string,
  plan: SubmissionPlan,
  postId?: string,
) {
  if (plan === "free") {
    throw Object.assign(new Error("Free-Plan kann keine Domains einreichen"), {
      code: "PLAN_NO_SUBMIT",
    });
  }
  const submissionStatus = plan === "legend" ? "in_review" : "pending";
  const db = usePrisma();
  return db.$transaction(async (tx) => {
    // Mark custom domain as submitted + type lesen für Submission
    const domain = await tx.userCustomDomain.update({
      where: { id: customDomainId, userId },
      data: { status: "submitted", postId: postId ?? null },
      select: { id: true, domain: true, type: true },
    });
    // Create submission record — type aus CustomDomain kopieren
    const submission = await tx.domainSubmission.create({
      data: {
        userId,
        domain: domain.domain,
        customDomainId,
        postId: postId ?? null,
        status: submissionStatus,
        type: domain.type,
      },
    });
    return { domain, submission };
  });
}

export async function castDomainVote(
  userId: string,
  submissionId: string,
  vote: "yes" | "no",
) {
  const db = usePrisma();
  return db.$transaction(async (tx) => {
    // Upsert vote (change allowed)
    const existing = await tx.domainVote.findUnique({
      where: { userId_submissionId: { userId, submissionId } },
    });
    if (existing) {
      if (existing.vote === vote) return { changed: false };
      await tx.domainVote.update({
        where: { userId_submissionId: { userId, submissionId } },
        data: { vote },
      });
    } else {
      await tx.domainVote.create({ data: { userId, submissionId, vote } });
    }

    // Recount
    const [yes, no] = await Promise.all([
      tx.domainVote.count({ where: { submissionId, vote: "yes" } }),
      tx.domainVote.count({ where: { submissionId, vote: "no" } }),
    ]);
    const updated = await tx.domainSubmission.update({
      where: { id: submissionId },
      data: { yesVotes: yes, noVotes: no },
    });

    // Netto-Yes (yes - no) muss die Schwelle erreichen, dann wandert die
    // Submission in die Admin-Review-Phase. Approval erfolgt erst durch Admin.
    let movedToReview = false;
    if (updated.status === "pending" && yes - no >= NET_VOTE_THRESHOLD) {
      await tx.domainSubmission.update({
        where: { id: submissionId },
        data: { status: "in_review" },
      });
      movedToReview = true;
    }

    return { yesVotes: yes, noVotes: no, movedToReview };
  });
}

async function approveDomainSubmissionTx(
  tx: any,
  submissionId: string,
  customDomainId: string,
  domain: string,
) {
  await tx.domainSubmission.update({
    where: { id: submissionId },
    data: { status: "approved", reviewedAt: new Date() },
  });
  await tx.userCustomDomain.update({
    where: { id: customDomainId },
    data: { status: "approved" },
  });
  // Add to global blocklist
  await tx.blocklistDomain.upsert({
    where: { domain },
    create: { domain, source: "community", isActive: true },
    update: { isActive: true },
  });
}

export async function adminApproveSubmission(
  submissionId: string,
  reviewNote?: string,
) {
  const db = usePrisma();
  const sub = await db.domainSubmission.findUniqueOrThrow({
    where: { id: submissionId },
    select: {
      customDomainId: true,
      domain: true,
      type: true,
      status: true,
      userId: true,
      postId: true,
    },
  });
  // Admin darf in beiden offenen Phasen approven (Vote oder Review)
  if (sub.status !== "pending" && sub.status !== "in_review") {
    throw new Error("Submission already resolved");
  }

  await db.$transaction((tx) =>
    approveDomainSubmissionTx(tx, submissionId, sub.customDomainId, sub.domain),
  );

  // Alle anderen User die diese Domain als Custom Domain haben → Slot freigeben + benachrichtigen
  const affectedDomains = await db.userCustomDomain.findMany({
    where: {
      domain: sub.domain,
      // Submitter's domain already handled in approveDomainSubmissionTx
      id: { not: sub.customDomainId },
      status: { notIn: ["approved", "rejected"] },
    },
    select: { id: true, userId: true },
  });

  if (affectedDomains.length > 0) {
    // Batch-Update: alle auf "approved" setzen (Slot freigeben)
    await db.userCustomDomain.updateMany({
      where: { id: { in: affectedDomains.map((d) => d.id) } },
      data: { status: "approved" },
    });

    // Notification an jeden betroffenen User
    await Promise.allSettled(
      affectedDomains.map((d) =>
        createNotification({
          recipientId: d.userId,
          type: "domain_accepted",
          actorName: "ReBreak",
          postId: sub.postId ?? undefined,
          preview: sub.domain,
        }),
      ),
    );
  }

  // Auch Submitter benachrichtigen (Admin hat seine Domain genehmigt)
  await createNotification({
    recipientId: sub.userId,
    type: "domain_accepted",
    actorName: "ReBreak Admin",
    postId: sub.postId ?? undefined,
    preview: sub.domain,
  }).catch(() => {});

  return db.domainSubmission.findUnique({
    where: { id: submissionId },
    select: { id: true, domain: true, type: true, userId: true, postId: true },
  });
}

export async function adminRejectSubmission(
  submissionId: string,
  reviewNote?: string,
) {
  const db = usePrisma();
  // Submission + zugehörige Custom-Domain laden (für Notification + Cleanup)
  const sub = await db.domainSubmission.findUniqueOrThrow({
    where: { id: submissionId },
    select: {
      userId: true,
      domain: true,
      postId: true,
      customDomainId: true,
    },
  });

  await db.$transaction(async (tx) => {
    await tx.domainSubmission.update({
      where: { id: submissionId },
      data: {
        status: "rejected",
        reviewedAt: new Date(),
        reviewNote: reviewNote ?? null,
      },
    });
    // Custom-Domain des Submitters komplett aus seiner Liste entfernen
    // (DomainSubmission via onDelete: Cascade automatisch mitgelöscht? NEIN —
    // Submission referenziert customDomain. Wir haben Submission gerade upgedatet,
    // also sicher löschen via Cascade von customDomain → submission.)
    await tx.userCustomDomain.delete({
      where: { id: sub.customDomainId },
    });
  });

  // Submitter benachrichtigen — Notification von ReBreak (System)
  await createNotification({
    recipientId: sub.userId,
    type: "domain_rejected",
    actorName: "ReBreak",
    postId: sub.postId ?? undefined,
    preview: sub.domain,
  }).catch(() => {});

  return { customDomainId: sub.customDomainId };
}

// 24 Stunden in Millisekunden — SLA für Admin-Approval.
// Legend-Requests sollen erst recht innerhalb dieser Frist beantwortet werden.
export const ADMIN_APPROVAL_SLA_MS = 24 * 60 * 60 * 1000;

export type PendingSubmissionRow = {
  id: string;
  domain: string;
  /** "web" | "mail_domain" — spiegelt den Type der eingereichten CustomDomain */
  type: string;
  yesVotes: number;
  noVotes: number;
  status: string;
  createdAt: Date;
  userId: string;
  postId: string | null;
  customDomain: { id: string } | null;
  user: { id: string; nickname: string | null; plan: string } | null;
  /** Plan-Priority — 2 = legend, 1 = pro, 0 = free/unknown */
  planPriority: number;
  /** ISO-deadline = createdAt + 24h. Wenn jetzt > deadline → overdue. */
  deadlineAt: Date;
  /** ms bis Deadline (negativ wenn überfällig) */
  msUntilDeadline: number;
};

/**
 * Pending Domain-Submissions für Admin-Review.
 *
 * Sort-Order:
 *   1. Legend zuerst (plan === "legend" hat höchste Priorität)
 *   2. Innerhalb gleicher Plan-Priority: älteste createdAt zuerst (FIFO)
 *
 * Postgres kann nach computed plan-priority nicht direkt orderBy'en, deswegen
 * sortieren wir post-fetch in JS — Liste ist klein (Admin-Review-Queue).
 *
 * Pro Row wird `deadlineAt` (createdAt + 24h) berechnet damit das Frontend
 * den Countdown / Overdue-Badge ohne Re-Computation zeigen kann.
 */
export async function getPendingSubmissions(): Promise<PendingSubmissionRow[]> {
  const db = usePrisma();
  const rows = await db.domainSubmission.findMany({
    where: { status: { in: ["pending", "in_review"] } },
    orderBy: [{ createdAt: "asc" }],
    select: {
      id: true,
      domain: true,
      type: true,
      yesVotes: true,
      noVotes: true,
      status: true,
      createdAt: true,
      userId: true,
      postId: true,
      customDomain: { select: { id: true } },
      user: { select: { id: true, nickname: true, plan: true } },
    },
  });

  const now = Date.now();
  const enriched = rows.map((r) => {
    const plan = r.user?.plan ?? "free";
    const planPriority = plan === "legend" ? 2 : plan === "pro" ? 1 : 0;
    const deadlineAt = new Date(r.createdAt.getTime() + ADMIN_APPROVAL_SLA_MS);
    return {
      ...r,
      planPriority,
      deadlineAt,
      msUntilDeadline: deadlineAt.getTime() - now,
    };
  });

  // Legend (2) vor Pro (1) vor Free (0). Bei Gleichstand: ältere zuerst.
  enriched.sort((a, b) => {
    if (a.planPriority !== b.planPriority) {
      return b.planPriority - a.planPriority;
    }
    return a.createdAt.getTime() - b.createdAt.getTime();
  });

  return enriched;
}

// ─── Global Blocklist ─────────────────────────────────────────────────────────

export async function getActiveBlocklistCount() {
  const db = usePrisma();
  return db.blocklistDomain.count({ where: { isActive: true } });
}

export async function getActiveBlocklistDomains() {
  const db = usePrisma();
  return db.blocklistDomain.findMany({
    where: { isActive: true },
    select: { domain: true },
  });
}

export async function isBlocklistedDomain(
  domain: string,
  userId: string,
): Promise<boolean> {
  const db = usePrisma();
  const [global, custom] = await Promise.all([
    db.blocklistDomain.findFirst({
      where: { domain, isActive: true },
      select: { domain: true },
    }),
    db.userCustomDomain.findFirst({
      where: { domain, userId },
      select: { domain: true },
    }),
  ]);
  return !!(global || custom);
}

export async function getBlocklistedDomainsSet(
  domains: string[],
  userId: string,
  includeGlobal = true,
): Promise<Set<string>> {
  if (domains.length === 0) return new Set();
  const db = usePrisma();
  const unique = [...new Set(domains)];

  // Alle möglichen Parent-Domains erzeugen: "mail.casino.de" → ["mail.casino.de", "casino.de"]
  const allVariants = [
    ...new Set(
      unique.flatMap((d) => {
        const parts = d.split(".");
        return parts
          .map((_, i) => parts.slice(i).join("."))
          .filter((v) => v.includes("."));
      }),
    ),
  ];

  const queries: Promise<{ domain: string }[]>[] = [];

  if (includeGlobal) {
    queries.push(
      db.blocklistDomain.findMany({
        where: { domain: { in: allVariants }, isActive: true },
        select: { domain: true },
      }),
    );
  }

  queries.push(
    db.userCustomDomain.findMany({
      where: { domain: { in: allVariants }, userId },
      select: { domain: true },
    }),
  );

  const results = await Promise.all(queries);
  const blockedSet = new Set(results.flatMap((r) => r.map((d) => d.domain)));

  // Zurück auf Original-Domains mappen: wenn irgendein Variant geblockt ist → Original blocken
  const result = new Set<string>();
  for (const orig of unique) {
    const parts = orig.split(".");
    const variants = parts
      .map((_, i) => parts.slice(i).join("."))
      .filter((v) => v.includes("."));
    if (variants.some((v) => blockedSet.has(v))) result.add(orig);
  }
  return result;
}

export async function upsertBlocklistDomains(
  domains: { domain: string; source: string }[],
) {
  const db = usePrisma();
  // Batch upsert in chunks
  const CHUNK = 5000;
  let total = 0;
  for (let i = 0; i < domains.length; i += CHUNK) {
    const chunk = domains.slice(i, i + CHUNK).map((d) => ({
      ...d,
      isActive: true,
    }));
    for (const d of chunk) {
      await db.blocklistDomain.upsert({
        where: { domain: d.domain },
        create: d,
        update: { isActive: true },
      });
      total++;
    }
  }
  return total;
}
