import { useCallback, useEffect, useMemo, useState } from "react";
import { Shield, Plus, Trash2, Search, CheckCircle2, AlertCircle, RefreshCw, Save, Mail, Building2, User as UserIcon } from "lucide-react";
import CmsLayout from "./CmsLayout";
import { PageHeader, SectionCard, TextInput, Select, Badge } from "./ui";
import { client } from "@/lib/api";
import UserPicker, { PickedUser } from "@/components/admin/UserPicker";

type RoleName = "admin" | "support" | "billing" | "client";

type UserRoleRecord = {
  id: number;
  target_user_id: string;
  role: RoleName | string;
  permissions?: string | null;
  created_at?: string | null;
  updated_at?: string | null;
};

type DraftRow = {
  id?: number; // existing DB id, undefined for new unsaved
  localKey: string;
  target_user_id: string;
  role: RoleName;
  permissions: string[];
  dirty: boolean;
  isNew: boolean;
  // Optional display metadata from the user picker (not persisted server-side).
  display_name?: string;
  display_email?: string;
  display_company?: string | null;
  display_avatar?: string | null;
};

const ALL_PERMS = [
  "manage_users", "view_billing", "issue_refunds", "manage_servers", "manage_domains",
  "manage_tickets", "view_audit", "edit_branding", "edit_pricing", "send_notifications",
  "manage_roles", "manage_site_settings", "view_whmcs_logs", "manage_plans",
];

const ROLES: RoleName[] = ["admin", "support", "billing", "client"];
const ROLE_COLOR: Record<RoleName, "green" | "blue" | "yellow" | "slate"> = {
  admin: "green",
  support: "blue",
  billing: "yellow",
  client: "slate",
};

const ROLE_DEFAULT_PERMS: Record<RoleName, string[]> = {
  admin: [...ALL_PERMS],
  support: ["manage_tickets", "manage_users", "manage_servers", "manage_domains"],
  billing: ["view_billing", "issue_refunds", "manage_tickets", "manage_plans"],
  client: [],
};

function parsePermissions(raw?: string | null): string[] {
  if (!raw) return [];
  const trimmed = raw.trim();
  if (!trimmed) return [];
  // Prefer JSON array, fall back to comma-separated list
  try {
    const parsed = JSON.parse(trimmed);
    if (Array.isArray(parsed)) return parsed.map((v) => String(v));
  } catch {
    // ignore JSON parse errors, treat as CSV below
  }
  return trimmed
    .split(",")
    .map((s) => s.trim())
    .filter(Boolean);
}

function serializePermissions(perms: string[]): string {
  return JSON.stringify(perms);
}

function makeLocalKey(): string {
  return `new-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
}

export default function CmsRoles() {
  const [rows, setRows] = useState<DraftRow[]>([]);
  const [loading, setLoading] = useState(false);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [toast, setToast] = useState<{ ok: boolean; msg: string } | null>(null);
  const [search, setSearch] = useState("");
  const [roleFilter, setRoleFilter] = useState<"all" | RoleName>("all");

  const notify = useCallback((ok: boolean, msg: string) => {
    setToast({ ok, msg });
    window.setTimeout(() => setToast(null), 3500);
  }, []);

  const callApi = useCallback(
    async <T,>(path: string, method: "GET" | "POST" | "PUT" | "DELETE" = "GET", data?: unknown): Promise<T> => {
      const res = await client.apiCall.invoke({
        url: path,
        method,
        data: data as Record<string, unknown> | undefined,
      });
      if (res?.error) {
        throw new Error(typeof res.error === "string" ? res.error : JSON.stringify(res.error));
      }
      return (res?.data ?? res) as T;
    },
    []
  );

  const load = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const res = await callApi<{ items: UserRoleRecord[] }>(
        "/api/v1/entities/user_roles/all?limit=1000&sort=-updated_at"
      );
      const items = res?.items ?? [];
      const mapped: DraftRow[] = items.map((it) => ({
        id: it.id,
        localKey: `row-${it.id}`,
        target_user_id: it.target_user_id || "",
        role: (ROLES.includes(it.role as RoleName) ? (it.role as RoleName) : "client"),
        permissions: parsePermissions(it.permissions),
        dirty: false,
        isNew: false,
      }));
      setRows(mapped);
    } catch (e) {
      const msg = e instanceof Error ? e.message : String(e);
      setError(`Failed to load role assignments: ${msg}`);
    } finally {
      setLoading(false);
    }
  }, [callApi]);

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

  const visible = useMemo(() => {
    const q = search.trim().toLowerCase();
    return rows.filter((r) => {
      if (roleFilter !== "all" && r.role !== roleFilter) return false;
      if (!q) return true;
      return (
        r.target_user_id.toLowerCase().includes(q) ||
        r.role.toLowerCase().includes(q) ||
        r.permissions.some((p) => p.toLowerCase().includes(q))
      );
    });
  }, [rows, search, roleFilter]);

  const updateRow = (localKey: string, patch: Partial<DraftRow>) => {
    setRows((prev) =>
      prev.map((r) => (r.localKey === localKey ? { ...r, ...patch, dirty: true } : r))
    );
  };

  const togglePerm = (localKey: string, perm: string) => {
    setRows((prev) =>
      prev.map((r) => {
        if (r.localKey !== localKey) return r;
        const has = r.permissions.includes(perm);
        const next = has ? r.permissions.filter((p) => p !== perm) : [...r.permissions, perm];
        return { ...r, permissions: next, dirty: true };
      })
    );
  };

  const applyRoleDefaults = (localKey: string, role: RoleName) => {
    setRows((prev) =>
      prev.map((r) =>
        r.localKey === localKey
          ? { ...r, role, permissions: [...ROLE_DEFAULT_PERMS[role]], dirty: true }
          : r
      )
    );
  };

  const addRow = () => {
    setRows((prev) => [
      {
        localKey: makeLocalKey(),
        target_user_id: "",
        role: "client",
        permissions: [...ROLE_DEFAULT_PERMS.client],
        dirty: true,
        isNew: true,
      },
      ...prev,
    ]);
  };

  // Set of already-assigned user identifiers (lower-cased) used to prevent
  // duplicate role assignments. A given local-key is excluded so the picker
  // doesn't flag the row's own current value.
  const assignedIdsExcept = useCallback(
    (excludeLocalKey: string): string[] => {
      const set = new Set<string>();
      for (const r of rows) {
        if (r.localKey === excludeLocalKey) continue;
        const id = (r.target_user_id || "").trim().toLowerCase();
        if (id) set.add(id);
      }
      return Array.from(set);
    },
    [rows]
  );

  const pickUserForRow = (row: DraftRow, user: PickedUser) => {
    const uid = (user.id || user.email || "").trim();
    if (!uid) {
      notify(false, "Selected user has no identifier (email).");
      return;
    }
    const existing = assignedIdsExcept(row.localKey);
    if (existing.includes(uid.toLowerCase())) {
      notify(false, `${user.name || uid} already has a role assignment.`);
      return;
    }
    updateRow(row.localKey, {
      target_user_id: uid,
      display_name: user.name,
      display_email: user.email,
      display_company: user.company ?? null,
      display_avatar: user.avatar ?? null,
    });
  };

  const removeRow = async (row: DraftRow) => {
    if (row.isNew || !row.id) {
      setRows((prev) => prev.filter((r) => r.localKey !== row.localKey));
      return;
    }
    if (!window.confirm(`Remove role assignment for "${row.target_user_id}"?`)) return;
    try {
      await callApi(`/api/v1/entities/user_roles/${row.id}`, "DELETE");
      setRows((prev) => prev.filter((r) => r.localKey !== row.localKey));
      notify(true, "Role assignment removed.");
    } catch (e) {
      const msg = e instanceof Error ? e.message : String(e);
      notify(false, `Delete failed: ${msg}`);
    }
  };

  const saveRow = async (row: DraftRow) => {
    const uid = row.target_user_id.trim();
    if (!uid) {
      notify(false, "Target user identifier is required.");
      return;
    }
    try {
      const payload = {
        target_user_id: uid,
        role: row.role,
        permissions: serializePermissions(row.permissions),
      };
      if (row.isNew || !row.id) {
        const created = await callApi<UserRoleRecord>(
          "/api/v1/entities/user_roles",
          "POST",
          payload
        );
        setRows((prev) =>
          prev.map((r) =>
            r.localKey === row.localKey
              ? {
                  ...r,
                  id: created.id,
                  target_user_id: uid,
                  dirty: false,
                  isNew: false,
                  localKey: `row-${created.id}`,
                }
              : r
          )
        );
        notify(true, "Role assignment created.");
      } else {
        await callApi<UserRoleRecord>(
          `/api/v1/entities/user_roles/${row.id}`,
          "PUT",
          payload
        );
        setRows((prev) =>
          prev.map((r) => (r.localKey === row.localKey ? { ...r, dirty: false } : r))
        );
        notify(true, "Role assignment updated.");
      }
    } catch (e) {
      const msg = e instanceof Error ? e.message : String(e);
      notify(false, `Save failed: ${msg}`);
    }
  };

  const saveAllDirty = async () => {
    const dirty = rows.filter((r) => r.dirty && r.target_user_id.trim());
    if (dirty.length === 0) {
      notify(true, "Nothing to save.");
      return;
    }
    setSaving(true);
    let ok = 0;
    let fail = 0;
    for (const row of dirty) {
      try {
        const payload = {
          target_user_id: row.target_user_id.trim(),
          role: row.role,
          permissions: serializePermissions(row.permissions),
        };
        if (row.isNew || !row.id) {
          const created = await callApi<UserRoleRecord>(
            "/api/v1/entities/user_roles",
            "POST",
            payload
          );
          setRows((prev) =>
            prev.map((r) =>
              r.localKey === row.localKey
                ? {
                    ...r,
                    id: created.id,
                    dirty: false,
                    isNew: false,
                    localKey: `row-${created.id}`,
                  }
                : r
            )
          );
        } else {
          await callApi<UserRoleRecord>(
            `/api/v1/entities/user_roles/${row.id}`,
            "PUT",
            payload
          );
          setRows((prev) =>
            prev.map((r) => (r.localKey === row.localKey ? { ...r, dirty: false } : r))
          );
        }
        ok += 1;
      } catch {
        fail += 1;
      }
    }
    setSaving(false);
    if (fail === 0) notify(true, `Saved ${ok} role assignment${ok === 1 ? "" : "s"}.`);
    else notify(false, `Saved ${ok}, failed ${fail}. Check the form and retry.`);
  };

  const dirtyCount = rows.filter((r) => r.dirty).length;

  return (
    <CmsLayout>
      <PageHeader
        icon={<Shield className="w-6 h-6" />}
        title="Roles Management"
        description="Assign admin, support, billing, or client roles to specific users and fine-tune their permissions. Local administrators have full access."
      />

      {error && (
        <div className="mb-4 p-3 rounded-lg bg-red-50 border border-red-200 text-sm text-red-800 flex items-center gap-2">
          <AlertCircle className="w-4 h-4" />
          {error}
        </div>
      )}

      {toast && (
        <div
          className={`mb-4 p-3 rounded-lg text-sm flex items-center gap-2 ${
            toast.ok
              ? "bg-emerald-50 border border-emerald-200 text-emerald-800"
              : "bg-red-50 border border-red-200 text-red-800"
          }`}
        >
          {toast.ok ? <CheckCircle2 className="w-4 h-4" /> : <AlertCircle className="w-4 h-4" />}
          {toast.msg}
        </div>
      )}

      <SectionCard
        title="Role Assignments"
        description={`${rows.length} total · ${dirtyCount} unsaved change${dirtyCount === 1 ? "" : "s"}`}
        actions={
          <div className="flex items-center gap-2">
            <button
              onClick={load}
              disabled={loading}
              className="px-3 py-1.5 rounded-lg bg-white border border-slate-300 text-slate-700 text-xs font-semibold inline-flex items-center gap-1 hover:bg-slate-50 disabled:opacity-50"
            >
              <RefreshCw className={`w-3.5 h-3.5 ${loading ? "animate-spin" : ""}`} />
              Refresh
            </button>
            <button
              onClick={addRow}
              className="px-3 py-1.5 rounded-lg bg-[#0b6e3e] text-white text-xs font-semibold inline-flex items-center gap-1 hover:bg-[#0a8048]"
            >
              <Plus className="w-3.5 h-3.5" /> Add Assignment
            </button>
            <button
              onClick={saveAllDirty}
              disabled={saving || dirtyCount === 0}
              className="px-3 py-1.5 rounded-lg bg-amber-500 text-white text-xs font-semibold inline-flex items-center gap-1 hover:bg-amber-600 disabled:opacity-50"
            >
              <Save className="w-3.5 h-3.5" />
              {saving ? "Saving…" : `Save All (${dirtyCount})`}
            </button>
          </div>
        }
      >
        <div className="flex flex-col md:flex-row gap-3 mb-4">
          <div className="relative flex-1">
            <Search className="w-4 h-4 absolute left-3 top-3 text-slate-400" />
            <TextInput
              placeholder="Search by user identifier, role, or permission…"
              value={search}
              onChange={(e) => setSearch(e.target.value)}
              className="!pl-9"
            />
          </div>
          <div className="w-full md:w-48">
            <Select
              value={roleFilter}
              onChange={(e) => setRoleFilter(e.target.value as "all" | RoleName)}
            >
              <option value="all">All roles</option>
              {ROLES.map((r) => (
                <option key={r} value={r}>
                  {r}
                </option>
              ))}
            </Select>
          </div>
        </div>

        {loading && (
          <div className="p-3 rounded-lg bg-blue-50 border border-blue-200 text-sm text-blue-800 mb-3">
            Loading role assignments…
          </div>
        )}

        <div className="space-y-4">
          {visible.map((row) => (
            <div
              key={row.localKey}
              className={`rounded-xl border p-4 ${
                row.dirty ? "border-amber-300 bg-amber-50/40" : "border-slate-200 bg-white"
              }`}
            >
              <div className="flex flex-col md:flex-row md:items-end gap-3 mb-3">
                <div className="flex-1 min-w-0">
                  <label className="block text-[11px] uppercase tracking-wider font-bold text-slate-500 mb-1">
                    Target User
                  </label>
                  {row.target_user_id ? (
                    <div className="flex items-center gap-2 px-2.5 py-2 rounded-lg border border-slate-300 bg-slate-50">
                      <div className="w-8 h-8 rounded-full bg-gradient-to-br from-[#0b6e3e] to-[#0a8048] text-white flex items-center justify-center text-[11px] font-bold shrink-0">
                        {row.display_avatar ? (
                          <img
                            src={row.display_avatar}
                            alt={row.display_name || row.target_user_id}
                            className="w-full h-full rounded-full object-cover"
                          />
                        ) : (
                          (row.display_name || row.target_user_id).slice(0, 2).toUpperCase()
                        )}
                      </div>
                      <div className="flex-1 min-w-0">
                        <div className="text-sm font-semibold text-slate-800 truncate flex items-center gap-2">
                          <UserIcon className="w-3.5 h-3.5 text-slate-400" />
                          {row.display_name || row.target_user_id}
                        </div>
                        <div className="flex items-center gap-3 text-xs text-slate-500">
                          {(row.display_email || row.target_user_id) && (
                            <span className="inline-flex items-center gap-1 truncate">
                              <Mail className="w-3 h-3" />
                              {row.display_email || row.target_user_id}
                            </span>
                          )}
                          {row.display_company && (
                            <span className="inline-flex items-center gap-1 truncate">
                              <Building2 className="w-3 h-3" />
                              {row.display_company}
                            </span>
                          )}
                        </div>
                      </div>
                      <button
                        type="button"
                        onClick={() =>
                          updateRow(row.localKey, {
                            target_user_id: "",
                            display_name: undefined,
                            display_email: undefined,
                            display_company: null,
                            display_avatar: null,
                          })
                        }
                        className="text-[11px] text-slate-500 hover:text-red-600 font-semibold px-2 py-1 rounded hover:bg-white"
                      >
                        Change
                      </button>
                    </div>
                  ) : (
                    <UserPicker
                      value=""
                      excludeIds={assignedIdsExcept(row.localKey)}
                      onSelect={(u) => pickUserForRow(row, u)}
                      placeholder="Search WHMCS clients by email, name, or company…"
                    />
                  )}
                </div>
                <div className="w-full md:w-48">
                  <label className="block text-[11px] uppercase tracking-wider font-bold text-slate-500 mb-1">
                    Role
                  </label>
                  <Select
                    value={row.role}
                    onChange={(e) => updateRow(row.localKey, { role: e.target.value as RoleName })}
                  >
                    {ROLES.map((r) => (
                      <option key={r} value={r}>
                        {r}
                      </option>
                    ))}
                  </Select>
                </div>
                <div className="flex items-center gap-2">
                  <Badge tone={ROLE_COLOR[row.role as RoleName] ?? "slate"}>
                    {row.role}
                  </Badge>
                  {row.dirty && <Badge tone="yellow">unsaved</Badge>}
                  {row.isNew && <Badge tone="blue">new</Badge>}
                </div>
              </div>

              <div className="flex items-center justify-between mb-2">
                <div className="text-xs font-semibold text-slate-700">
                  Permissions ({row.permissions.length} selected)
                </div>
                <button
                  onClick={() => applyRoleDefaults(row.localKey, row.role as RoleName)}
                  className="text-[11px] text-[#0b6e3e] font-semibold hover:underline"
                >
                  Apply defaults for "{row.role}"
                </button>
              </div>
              <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 mb-3">
                {ALL_PERMS.map((perm) => {
                  const has = row.permissions.includes(perm);
                  return (
                    <button
                      key={perm}
                      onClick={() => togglePerm(row.localKey, perm)}
                      className={`flex items-center gap-2 px-2.5 py-1.5 rounded-lg border text-xs font-mono transition ${
                        has
                          ? "bg-[#0b6e3e] border-[#0b6e3e] text-white"
                          : "bg-white border-slate-300 text-slate-700 hover:bg-slate-50"
                      }`}
                      type="button"
                    >
                      <span
                        className={`w-3.5 h-3.5 rounded border flex items-center justify-center text-[10px] font-bold ${
                          has ? "bg-white text-[#0b6e3e] border-white" : "border-slate-400 text-transparent"
                        }`}
                      >
                        ✓
                      </span>
                      {perm}
                    </button>
                  );
                })}
              </div>

              <div className="flex items-center justify-end gap-2 pt-2 border-t border-slate-100">
                <button
                  onClick={() => removeRow(row)}
                  className="px-2.5 py-1.5 rounded-lg border border-red-200 text-red-600 text-xs font-semibold inline-flex items-center gap-1 hover:bg-red-50"
                >
                  <Trash2 className="w-3.5 h-3.5" /> Remove
                </button>
                <button
                  onClick={() => saveRow(row)}
                  disabled={!row.dirty}
                  className="px-3 py-1.5 rounded-lg bg-[#0b6e3e] text-white text-xs font-semibold inline-flex items-center gap-1 hover:bg-[#0a8048] disabled:opacity-50"
                >
                  <Save className="w-3.5 h-3.5" /> Save
                </button>
              </div>
            </div>
          ))}

          {!loading && visible.length === 0 && (
            <div className="text-center py-10 text-slate-500 text-sm border border-dashed border-slate-300 rounded-xl">
              No role assignments yet. Click <span className="font-semibold">Add Assignment</span> to grant admin or staff access to a user.
            </div>
          )}
        </div>
      </SectionCard>

      <div className="h-6" />

      <SectionCard
        title="About Roles & Permissions"
        description="How role assignments resolve at runtime."
      >
        <ul className="text-sm text-slate-700 space-y-2 list-disc pl-5">
          <li>
            <b>admin</b> — Full access to the admin CMS, diagnostics, WHMCS tools,
            users, roles, branding and billing. Assign this to trusted local administrators.
          </li>
          <li>
            <b>support</b> — Day-to-day helpdesk operations: manage tickets, users,
            servers and domains.
          </li>
          <li>
            <b>billing</b> — Finance operations: view billing, issue refunds, manage plans.
          </li>
          <li>
            <b>client</b> — Default role for end-customers; no admin access.
          </li>
          <li>
            Role assignments are resolved by the backend when a user authenticates
            (including via WHMCS). Local assignments here take precedence over
            WHMCS-provided defaults.
          </li>
        </ul>
      </SectionCard>
    </CmsLayout>
  );
}