import { useCallback, useEffect, useState } from "react";
import { apiFetch } from "../lib/api";
import { resolveVipCountry } from "./useWebContentDomains";
import { useBlockerStatsStore } from "../stores/blockerStats";

export type DomainStatus = "active" | "submitted" | "approved" | "rejected";

export type EntryKind = "web" | "mail_domain" | "mail_display_name";

export type CustomDomain = {
  id: string;
  domain: string;
  type?: EntryKind;
  status: DomainStatus;
  addedAt?: string;
  postId?: string | null;
  submission?: {
    id: string;
    yesVotes: number;
    noVotes: number;
    status: string;
  } | null;
  vipDeferUntil?: string | null;
  vipEvictAt?: string | null;
};

export type Plan = "free" | "pro" | "legend";

/**
 * Ergebnis von addDomain. Neben `ok` transportiert es die 3-Fall-Logik des
 * Backends für Web-Domains gegen Layer 1 (global) + Layer 2 (kuratierte VIP):
 *   alreadyGlobal    — Mail-Pattern schon global → kein Slot verbrannt
 *   alreadyProtected — Web-Domain in global UND kuratierter VIP → nichts zu tun
 *   inGlobalNotVip   — Web-Domain in global, NICHT in kuratierter VIP →
 *                      User kann sie per addToVip-Re-Request zur VIP nehmen
 *   addedToVip       — Domain wurde als VIP-Zweitschutz ('approved') gespeichert
 */
export type AddDomainResult = {
  ok: boolean;
  error?: string;
  alreadyGlobal?: boolean;
  alreadyProtected?: boolean;
  inGlobalNotVip?: boolean;
  addedToVip?: boolean;
  vipFull?: boolean;
  newDomainId?: string;
};

export type Tier = {
  plan: Plan;
  domainLimit: number; // pro=10, legend=20 (web + mail gemeinsam)
  refillEnabled: boolean; // pro/legend=true
  globalBlocklist: boolean; // pro/legend=true
  canSubmit: boolean; // pro/legend=true
  usedSlots: number; // active+submitted (NICHT approved/rejected)
  atLimit: boolean;
};

function deriveTier(plan: Plan, domains: CustomDomain[]): Tier {
  // Slots: EIN gemeinsamer Pool für web + mail. Free-Tier ist entfallen.
  const limit = plan === "legend" ? 20 : 10;
  const refill = plan !== "free";
  const usedSlots = domains.filter(
    (d) => d.status === "active" || d.status === "submitted",
  ).length;
  return {
    plan,
    domainLimit: limit,
    refillEnabled: refill,
    globalBlocklist: refill,
    canSubmit: refill,
    usedSlots,
    atLimit: usedSlots >= limit,
  };
}

export type UseCustomDomainsReturn = {
  domains: CustomDomain[];
  tier: Tier;
  /** Belegte Slots (active + submitted) — web + mail gemeinsamer Pool. */
  count: number;
  /** Slot-Limit gesamt (Pro 10 / Legend 20). */
  limit: number;
  loading: boolean;
  error: string | null;
  refresh: () => Promise<void>;
  addDomain: (
    pattern: string,
    kind?: "web" | "mail",
    opts?: { addToVip?: boolean },
  ) => Promise<AddDomainResult>;
  submitDomain: (id: string) => Promise<{ ok: boolean; error?: string }>;
  removeDomain: (id: string) => Promise<{ ok: boolean; error?: string }>;
  submitVipSwap: (
    newDomainId: string,
    evictedDomainId: string,
  ) => Promise<{ ok: boolean; error?: string }>;
  /** Live-Validate (regex) ob string gültiger Domain-Name ist. */
  isValidDomain: (s: string) => boolean;
  /** Normalize: lowercase, http(s)://, /path stripping, www. weg. */
  normalizeDomain: (s: string) => string;
};

const DOMAIN_REGEX = /^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/i;

export function normalizeDomain(input: string): string {
  let s = input.trim().toLowerCase();
  if (s.startsWith("https://")) s = s.slice(8);
  else if (s.startsWith("http://")) s = s.slice(7);
  const slash = s.indexOf("/");
  if (slash >= 0) s = s.slice(0, slash);
  if (s.startsWith("www.")) s = s.slice(4);
  return s;
}

export function isValidDomain(input: string): boolean {
  const n = normalizeDomain(input);
  if (!n || n.length > 253) return false;
  return DOMAIN_REGEX.test(n);
}

/**
 * Public-/Freemail-Provider — dürfen NIE als Custom-Domain (web ODER mail)
 * geblockt werden: icloud.com/gmail.com zu blocken würde die ganze Mail/Webmail
 * des Users sperren. Realer Vorfall: User kopiert eine Casino-Spam-Adresse
 * `xyz@icloud.com` komplett ins Feld → wir extrahieren `icloud.com`.
 * Spiegel-Liste im Backend: `backend/server/utils/public-email-domains.ts`
 * — bei Änderungen beide synchron halten.
 */
const PUBLIC_EMAIL_DOMAINS = new Set<string>([
  "gmail.com",
  "googlemail.com",
  "icloud.com",
  "me.com",
  "mac.com",
  "outlook.com",
  "outlook.de",
  "hotmail.com",
  "hotmail.de",
  "hotmail.co.uk",
  "hotmail.fr",
  "live.com",
  "live.de",
  "msn.com",
  "yahoo.com",
  "yahoo.de",
  "yahoo.co.uk",
  "yahoo.fr",
  "ymail.com",
  "rocketmail.com",
  "gmx.de",
  "gmx.net",
  "gmx.at",
  "gmx.ch",
  "gmx.com",
  "web.de",
  "aol.com",
  "aim.com",
  "proton.me",
  "protonmail.com",
  "pm.me",
  "tutanota.com",
  "tutanota.de",
  "tuta.io",
  "posteo.de",
  "posteo.net",
  "mailbox.org",
  "hey.com",
  "t-online.de",
  "freenet.de",
  "arcor.de",
  "mail.com",
  "mail.de",
  "email.de",
  "zoho.com",
  "fastmail.com",
  "fastmail.fm",
  "hushmail.com",
  "yandex.com",
  "yandex.ru",
  "mail.ru",
  "laposte.net",
  "orange.fr",
  "free.fr",
  "sfr.fr",
  "wanadoo.fr",
  "qq.com",
  "163.com",
  "126.com",
  "naver.com",
  "daum.net",
]);

export function isPublicEmailDomain(domain: string): boolean {
  return PUBLIC_EMAIL_DOMAINS.has(domain.trim().toLowerCase());
}

/**
 * Custom-Domain CRUD gegen `/api/custom-domains/*` mit Tier-aware Limits.
 *
 * Slot-Modell (Single-Source-of-Truth: User.plan):
 *   Pro    → 10 Slots, Refill bei approved/rejected, Submit erlaubt
 *   Legend → 20 Slots, Refill, Submit
 * web + mail teilen sich EINEN gemeinsamen Slot-Pool. Free-Tier ist entfallen.
 */
export function useCustomDomains(plan: Plan): UseCustomDomainsReturn {
  const [domains, setDomains] = useState<CustomDomain[]>([]);
  const [apiCount, setApiCount] = useState<number | null>(null);
  const [apiLimit, setApiLimit] = useState<number | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  const fetchDomains = useCallback(async () => {
    try {
      // Backend `GET /api/custom-domains` returns
      //   { items: CustomDomain[], count: number, limit: number }
      // (count/limit = gemeinsamer web+mail-Slot-Pool). Array-Fallback deckt
      // eine ältere Response-Form ab, falls ein gecachter Client das hier
      // trifft bevor das Deploy landet.
      const res = await apiFetch<
        | CustomDomain[]
        | {
            items?: CustomDomain[];
            domains?: CustomDomain[];
            count?: number;
            limit?: number;
          }
      >("/api/custom-domains");
      let arr: CustomDomain[] = [];
      let count: number | null = null;
      let limit: number | null = null;
      if (Array.isArray(res)) {
        arr = res;
      } else if (res) {
        arr = (res as any).items ?? (res as any).domains ?? [];
        count =
          typeof (res as any).count === "number" ? (res as any).count : null;
        limit =
          typeof (res as any).limit === "number" ? (res as any).limit : null;
      }
      setDomains(arr);
      setApiCount(count);
      setApiLimit(limit);
      setError(null);
    } catch (e: any) {
      console.error("[useCustomDomains] fetch failed:", e?.message ?? e);
      setError(e?.message ?? "unknown");
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    fetchDomains();
  }, [fetchDomains]);

  const addDomain = useCallback(
    async (
      input: string,
      kind?: "web" | "mail",
      opts?: { addToVip?: boolean },
    ): Promise<AddDomainResult> => {
      const resolvedKind: "web" | "mail" =
        kind ?? (input.includes("@") ? "mail" : "web");
      if (resolvedKind === "web" && !isValidDomain(input))
        return { ok: false, error: "invalid_domain" };
      if (resolvedKind === "mail" && !input.trim())
        return { ok: false, error: "invalid_pattern" };
      // Slot-Limit-Vorabcheck gegen den Backend-count/limit (Single Source of
      // Truth — EIN gemeinsamer Pool). Wenn die API noch keine count/limit
      // geliefert hat → skip, das Backend rejected dann mit LIMIT_REACHED.
      // Entfällt bei addToVip: 'approved'-Einträge belegen keinen Slot.
      if (
        !opts?.addToVip &&
        apiCount != null &&
        apiLimit != null &&
        apiCount >= apiLimit
      ) {
        return { ok: false, error: "limit_reached" };
      }
      const pattern =
        resolvedKind === "web" ? normalizeDomain(input) : input.trim();
      // Public-/Freemail-Domain (icloud.com, gmail.com …) hart ablehnen — web UND
      // mail. Sonst würde das Blocken die gesamte Mail/Webmail des Users sperren.
      const domainToCheck =
        resolvedKind === "mail" && pattern.includes("@")
          ? pattern.slice(pattern.lastIndexOf("@") + 1)
          : pattern;
      if (isPublicEmailDomain(domainToCheck))
        return { ok: false, error: "public_domain" };
      const body: Record<string, string | boolean> = { pattern };
      if (kind !== undefined) body.kind = kind;
      // Land mitschicken — Backend prüft die kuratierte VIP-Liste des Landes.
      if (resolvedKind === "web") body.country = resolveVipCountry();
      if (opts?.addToVip) body.addToVip = true;
      try {
        const res = await apiFetch<any>("/api/custom-domains", {
          method: "POST",
          body,
        });
        if (res?.alreadyGlobal) return { ok: false, alreadyGlobal: true };
        if (res?.alreadyProtected) return { ok: false, alreadyProtected: true };
        if (res?.inGlobalNotVip) return { ok: false, inGlobalNotVip: true };
        await fetchDomains();
        if (res?.vipFull)
          return { ok: true, vipFull: true, newDomainId: res.id };
        return { ok: true, addedToVip: res?.addedToVip === true };
      } catch (e: any) {
        return { ok: false, error: e?.message ?? "add_failed" };
      }
    },
    [apiCount, apiLimit, fetchDomains],
  );

  const submitDomain = useCallback(
    async (id: string) => {
      const tier = deriveTier(plan, domains);
      if (!tier.canSubmit)
        return { ok: false, error: "plan_does_not_support_submit" };
      try {
        await apiFetch(`/api/custom-domains/${id}/submit`, {
          method: "POST",
          body: {},
        });
        // Optimistisches lokales Update: Half-Donut im ProtectionDetailsSheet
        // soll sofort die neue Freigabe zeigen, ohne 60s auf Stats-Refresh zu warten.
        useBlockerStatsStore.getState().bumpMyInReview(1);
        await fetchDomains();
        return { ok: true };
      } catch (e: any) {
        return { ok: false, error: e?.message ?? "submit_failed" };
      }
    },
    [plan, domains, fetchDomains],
  );

  const removeDomain = useCallback(
    async (id: string) => {
      try {
        await apiFetch(`/api/custom-domains/${id}`, { method: "DELETE" });
        await fetchDomains();
        return { ok: true };
      } catch (e: any) {
        return { ok: false, error: e?.message ?? "remove_failed" };
      }
    },
    [fetchDomains],
  );

  const submitVipSwap = useCallback(
    async (newDomainId: string, evictedDomainId: string) => {
      try {
        await apiFetch("/api/custom-domains/vip-swap", {
          method: "POST",
          body: { newDomainId, evictedDomainId },
        });
        await fetchDomains();
        return { ok: true };
      } catch (e: any) {
        return { ok: false, error: e?.message ?? "vip_swap_failed" };
      }
    },
    [fetchDomains],
  );

  const tier = deriveTier(plan, domains);

  // API-Werte bevorzugen (Single Source of Truth); lokale Ableitung als
  // Fallback, damit die UI auch bei einem stale-bundle-Moment funktioniert.
  const count: number =
    apiCount ??
    domains.filter((d) => d.status === "active" || d.status === "submitted")
      .length;
  const limit: number = apiLimit ?? (plan === "legend" ? 20 : 10);

  return {
    domains,
    tier,
    count,
    limit,
    loading,
    error,
    refresh: fetchDomains,
    addDomain,
    submitDomain,
    removeDomain,
    submitVipSwap,
    isValidDomain,
    normalizeDomain,
  };
}
