import { useState, useEffect, useCallback } from 'react';
import { ChevronUp, ChevronDown, Pencil, X, Check } from 'lucide-react';
import { Button, Input, Select, ConfirmDialog, useToast } from '@cameleer/design-system';
import {
  useClaimMappingRules,
  useCreateClaimMappingRule,
  useUpdateClaimMappingRule,
  useDeleteClaimMappingRule,
  useTestClaimMappingRules,
} from '../../api/queries/admin/claim-mappings';
import type { ClaimMappingRule, TestResponse } from '../../api/queries/admin/claim-mappings';
import { useRoles, useGroups } from '../../api/queries/admin/rbac';
import { describeApiError } from '../../api/errors';
import styles from './ClaimMappingRulesModal.module.css';

const MATCH_OPTIONS = [
  { value: 'equals', label: 'equals' },
  { value: 'contains', label: 'contains' },
  { value: 'regex', label: 'regex' },
];

const ACTION_OPTIONS = [
  { value: 'assignRole', label: 'assign role' },
  { value: 'addToGroup', label: 'add to group' },
];

const EMPTY_FORM = { claim: '', matchType: 'equals', matchValue: '', action: 'assignRole', target: '' };

const TEST_PLACEHOLDER = `{
  "sub": "user-42",
  "email": "jane@acme.com",
  "department": "engineering",
  "groups": ["frontend", "design"]
}`;

// Local rule type — server rules have string IDs, new rules get temp- prefixed IDs
interface LocalRule {
  id: string;
  claim: string;
  matchType: string;
  matchValue: string;
  action: string;
  target: string;
  priority: number;
}

function toLocal(rule: ClaimMappingRule): LocalRule {
  return { id: rule.id, claim: rule.claim, matchType: rule.matchType, matchValue: rule.matchValue, action: rule.action, target: rule.target, priority: rule.priority };
}

interface Props {
  open: boolean;
  onClose: () => void;
}

export default function ClaimMappingRulesModal({ open, onClose }: Props) {
  const { toast } = useToast();
  const { data: serverRules = [], isLoading } = useClaimMappingRules();
  const createRule = useCreateClaimMappingRule();
  const updateRule = useUpdateClaimMappingRule();
  const deleteRule = useDeleteClaimMappingRule();
  const testRules = useTestClaimMappingRules();

  // Local working copy — only saved to server on Apply
  const [localRules, setLocalRules] = useState<LocalRule[]>([]);
  const [dirty, setDirty] = useState(false);

  // Add form
  const [addForm, setAddForm] = useState({ ...EMPTY_FORM });

  // Edit state
  const [editingId, setEditingId] = useState<string | null>(null);
  const [editForm, setEditForm] = useState({ ...EMPTY_FORM });

  // Delete confirm
  const [deleteTarget, setDeleteTarget] = useState<LocalRule | null>(null);

  // Apply state
  const [applying, setApplying] = useState(false);

  // Test panel
  const [testOpen, setTestOpen] = useState(false);
  const [testInput, setTestInput] = useState('');
  const [testResult, setTestResult] = useState<TestResponse | null>(null);
  const [testError, setTestError] = useState('');

  // Roles and groups for target dropdown
  const { data: roles = [] } = useRoles(open);
  const { data: groups = [] } = useGroups(open);

  const roleOptions = roles.map((r) => ({ value: r.name, label: r.name }));
  const groupOptions = groups.map((g) => ({ value: g.name, label: g.name }));

  function targetOptions(action: string) {
    const opts = action === 'assignRole' ? roleOptions : groupOptions;
    return [{ value: '', label: action === 'assignRole' ? 'Select role...' : 'Select group...' }, ...opts];
  }

  // Initialize local rules from server when modal opens or server data arrives
  useEffect(() => {
    if (open && !dirty) {
      setLocalRules(serverRules.map(toLocal));
    }
  }, [open, serverRules, dirty]);

  // Reset state when modal closes
  const handleClose = useCallback(() => {
    setDirty(false);
    setEditingId(null);
    setEditForm({ ...EMPTY_FORM });
    setAddForm({ ...EMPTY_FORM });
    setDeleteTarget(null);
    setTestResult(null);
    setTestError('');
    onClose();
  }, [onClose]);

  if (!open) return null;

  const sorted = [...localRules].sort((a, b) => a.priority - b.priority);
  const matchedRuleIds = new Set((testResult?.matchedRules ?? []).map((r) => r.ruleId));

  // ── Local Handlers (no API calls) ─────────────────────────────────

  function handleAdd() {
    const maxPriority = sorted.length > 0 ? Math.max(...sorted.map((r) => r.priority)) : -1;
    const newRule: LocalRule = {
      id: `temp-${crypto.randomUUID()}`,
      claim: addForm.claim.trim(),
      matchType: addForm.matchType,
      matchValue: addForm.matchValue.trim(),
      action: addForm.action,
      target: addForm.target,
      priority: maxPriority + 1,
    };
    setLocalRules((prev) => [...prev, newRule]);
    setAddForm({ ...EMPTY_FORM });
    setDirty(true);
    setTestResult(null);
  }

  function startEdit(rule: LocalRule) {
    setEditingId(rule.id);
    setEditForm({
      claim: rule.claim,
      matchType: rule.matchType,
      matchValue: rule.matchValue,
      action: rule.action,
      target: rule.target,
    });
  }

  function saveEdit(rule: LocalRule) {
    setLocalRules((prev) =>
      prev.map((r) =>
        r.id === rule.id
          ? { ...r, claim: editForm.claim.trim(), matchType: editForm.matchType, matchValue: editForm.matchValue.trim(), action: editForm.action, target: editForm.target }
          : r,
      ),
    );
    setEditingId(null);
    setDirty(true);
    setTestResult(null);
  }

  function confirmDelete() {
    if (!deleteTarget) return;
    setLocalRules((prev) => prev.filter((r) => r.id !== deleteTarget.id));
    setDeleteTarget(null);
    setDirty(true);
    setTestResult(null);
  }

  function handleMove(rule: LocalRule, direction: 'up' | 'down') {
    const idx = sorted.findIndex((r) => r.id === rule.id);
    const swapIdx = direction === 'up' ? idx - 1 : idx + 1;
    if (swapIdx < 0 || swapIdx >= sorted.length) return;

    const other = sorted[swapIdx];
    setLocalRules((prev) =>
      prev.map((r) => {
        if (r.id === rule.id) return { ...r, priority: other.priority };
        if (r.id === other.id) return { ...r, priority: rule.priority };
        return r;
      }),
    );
    setDirty(true);
    setTestResult(null);
  }

  // ── Apply (save all changes to server) ────────────────────────────

  async function handleApply() {
    setApplying(true);
    try {
      const serverIds = new Set(serverRules.map((r) => r.id));
      const localIds = new Set(localRules.map((r) => r.id));

      // Delete: rules on server but not in local
      const toDelete = serverRules.filter((r) => !localIds.has(r.id));
      for (const r of toDelete) {
        await deleteRule.mutateAsync(r.id);
      }

      // Create: rules in local with temp- prefix
      for (const r of localRules) {
        if (r.id.startsWith('temp-')) {
          await createRule.mutateAsync({
            claim: r.claim, matchType: r.matchType, matchValue: r.matchValue,
            action: r.action, target: r.target, priority: r.priority,
          });
        }
      }

      // Update: rules that exist on both sides but have changed
      for (const local of localRules) {
        if (local.id.startsWith('temp-')) continue;
        if (!serverIds.has(local.id)) continue;
        const server = serverRules.find((r) => r.id === local.id);
        if (!server) continue;
        const changed = server.claim !== local.claim || server.matchType !== local.matchType
          || server.matchValue !== local.matchValue || server.action !== local.action
          || server.target !== local.target || server.priority !== local.priority;
        if (changed) {
          await updateRule.mutateAsync({
            id: local.id, claim: local.claim, matchType: local.matchType,
            matchValue: local.matchValue, action: local.action, target: local.target,
            priority: local.priority,
          });
        }
      }

      toast({ title: 'Rules saved', variant: 'success' });
      handleClose();
    } catch (e) {
      toast({ title: 'Failed to save rules', description: describeApiError(e), variant: 'error', duration: 86_400_000 });
    } finally {
      setApplying(false);
    }
  }

  // ── Test ──────────────────────────────────────────────────────────

  function handleTest() {
    setTestError('');
    setTestResult(null);
    let claims: Record<string, unknown>;
    try {
      claims = JSON.parse(testInput);
    } catch {
      setTestError('Invalid JSON — paste a decoded JWT claims object');
      return;
    }
    const rules = localRules.map((r) => ({
      id: r.id, claim: r.claim, matchType: r.matchType,
      matchValue: r.matchValue, action: r.action, target: r.target, priority: r.priority,
    }));
    testRules.mutate({ rules, claims }, {
      onSuccess: (result) => setTestResult(result),
      onError: (e) => setTestError(describeApiError(e)),
    });
  }

  // ── Render helpers ────────────────────────────────────────────────

  function actionPillClass(action: string) {
    return action === 'assignRole' ? styles.pillAssignRole : styles.pillAddToGroup;
  }

  function actionLabel(action: string) {
    return action === 'assignRole' ? 'assign role' : 'add to group';
  }

  const addDisabled = !addForm.claim.trim() || !addForm.matchValue.trim() || !addForm.target;

  // ── JSX ───────────────────────────────────────────────────────────

  return (
    <div className={styles.overlay} onClick={handleClose}>
      <div className={styles.modal} onClick={(e) => e.stopPropagation()}>
        {/* Header */}
        <div className={styles.header}>
          <div className={styles.headerInfo}>
            <div className={styles.title}>Claim Mapping Rules</div>
            <div className={styles.subtitle}>Map JWT claims to Cameleer roles and groups. Evaluated on every OIDC login.</div>
          </div>
          <button className={styles.iconBtn} onClick={handleClose}><X size={16} /></button>
        </div>

        {/* Body */}
        <div className={styles.body}>
          {isLoading ? (
            <div className={styles.emptyState}>Loading...</div>
          ) : sorted.length === 0 ? (
            <div className={styles.emptyState}>
              No claim mapping rules configured.<br />
              Rules let you assign roles and groups based on JWT claims.
            </div>
          ) : (
            <table className={styles.table}>
              <thead>
                <tr>
                  <th>#</th>
                  <th>Claim</th>
                  <th>Match</th>
                  <th>Value</th>
                  <th>Action</th>
                  <th>Target</th>
                  <th></th>
                </tr>
              </thead>
              <tbody>
                {sorted.map((rule, idx) => {
                  const isEditing = editingId === rule.id;
                  const isMatched = matchedRuleIds.has(rule.id);
                  return (
                    <tr key={rule.id} className={isMatched ? styles.matchedRow : undefined}>
                      <td className={styles.priorityCell}>{idx + 1}</td>
                      {isEditing ? (
                        <>
                          <td><Input value={editForm.claim} onChange={(e) => setEditForm({ ...editForm, claim: e.target.value })} className={styles.claimInput} /></td>
                          <td><Select options={MATCH_OPTIONS} value={editForm.matchType} onChange={(e) => setEditForm({ ...editForm, matchType: e.target.value })} /></td>
                          <td><Input value={editForm.matchValue} onChange={(e) => setEditForm({ ...editForm, matchValue: e.target.value })} /></td>
                          <td><Select options={ACTION_OPTIONS} value={editForm.action} onChange={(e) => setEditForm({ ...editForm, action: e.target.value, target: '' })} /></td>
                          <td><Select options={targetOptions(editForm.action)} value={editForm.target} onChange={(e) => setEditForm({ ...editForm, target: e.target.value })} /></td>
                          <td>
                            <div className={styles.actions}>
                              <button className={styles.iconBtn} onClick={() => saveEdit(rule)} title="Save"><Check size={14} /></button>
                              <button className={styles.iconBtn} onClick={() => setEditingId(null)} title="Cancel"><X size={14} /></button>
                            </div>
                          </td>
                        </>
                      ) : (
                        <>
                          <td><code className={styles.claimCode}>{rule.claim}</code></td>
                          <td><span className={`${styles.pill} ${styles.pillMatch}`}>{rule.matchType}</span></td>
                          <td><span className={styles.matchValue}>{rule.matchValue}</span></td>
                          <td><span className={`${styles.pill} ${actionPillClass(rule.action)}`}>{actionLabel(rule.action)}</span></td>
                          <td className={styles.targetCell}>{rule.target}</td>
                          <td>
                            <div className={styles.actions}>
                              {isMatched && <span className={styles.matchCheck}><Check size={14} /></span>}
                              <button className={styles.iconBtn} onClick={() => handleMove(rule, 'up')} disabled={idx === 0} title="Move up"><ChevronUp size={14} /></button>
                              <button className={styles.iconBtn} onClick={() => handleMove(rule, 'down')} disabled={idx === sorted.length - 1} title="Move down"><ChevronDown size={14} /></button>
                              <button className={styles.iconBtn} onClick={() => startEdit(rule)} title="Edit"><Pencil size={14} /></button>
                              <button className={styles.iconBtn} onClick={() => setDeleteTarget(rule)} title="Delete"><X size={14} /></button>
                            </div>
                          </td>
                        </>
                      )}
                    </tr>
                  );
                })}
              </tbody>
            </table>
          )}

          {/* Add row */}
          <div className={styles.addRow}>
            <Input placeholder="Claim name" value={addForm.claim} onChange={(e) => setAddForm({ ...addForm, claim: e.target.value })} className={styles.claimInput} />
            <Select options={MATCH_OPTIONS} value={addForm.matchType} onChange={(e) => setAddForm({ ...addForm, matchType: e.target.value })} className={styles.matchSelect} />
            <Input placeholder="Match value" value={addForm.matchValue} onChange={(e) => setAddForm({ ...addForm, matchValue: e.target.value })} className={styles.valueInput} />
            <Select options={ACTION_OPTIONS} value={addForm.action} onChange={(e) => setAddForm({ ...addForm, action: e.target.value, target: '' })} className={styles.actionSelect} />
            <Select options={targetOptions(addForm.action)} value={addForm.target} onChange={(e) => setAddForm({ ...addForm, target: e.target.value })} className={styles.targetInput} />
            <Button size="sm" variant="primary" onClick={handleAdd} disabled={addDisabled}>+ Add</Button>
          </div>

          {/* Test panel */}
          <div className={styles.testToggle} onClick={() => setTestOpen(!testOpen)}>
            {testOpen ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
            <span className={styles.testToggleLabel}>Test Rules</span>
            <span className={styles.testToggleHint}>Paste a decoded JWT to preview which rules would fire</span>
          </div>

          {testOpen && (
            <div className={styles.testPanel}>
              <div className={styles.testTextarea}>
                <textarea
                  placeholder={TEST_PLACEHOLDER}
                  value={testInput}
                  onChange={(e) => setTestInput(e.target.value)}
                />
                <Button size="sm" variant="secondary" onClick={handleTest} loading={testRules.isPending}>
                  Test
                </Button>
              </div>
              <div className={styles.testResults}>
                <div className={styles.testResultsTitle}>Result</div>
                {testError && <div className={styles.testError}>{testError}</div>}
                {!testResult && !testError && <div className={styles.testEmpty}>Paste claims and click Test</div>}
                {testResult && testResult.fallback && (
                  <div className={styles.testFallback}>No rules matched — would fall back to default roles</div>
                )}
                {testResult && !testResult.fallback && (
                  <>
                    {testResult.matchedRules.map((m) => (
                      <div key={m.ruleId} className={styles.testMatch}>
                        <div className={styles.testMatchLabel}>&#10003; Rule #{sorted.findIndex((r) => r.id === m.ruleId) + 1} matched</div>
                        <div className={styles.testMatchDetail}>
                          <code className={styles.claimCode}>{m.claim}</code>{' '}
                          {m.matchType} <code>{m.matchValue}</code>{' '}
                          &rarr; {actionLabel(m.action)} <strong>{m.target}</strong>
                        </div>
                      </div>
                    ))}
                    <div className={styles.testEffective}>
                      {testResult.effectiveRoles.length > 0 && (
                        <div>Roles: <span className={styles.testEffectiveValues}>{testResult.effectiveRoles.join(', ')}</span></div>
                      )}
                      {testResult.effectiveGroups.length > 0 && (
                        <div>Groups: <span className={styles.testEffectiveValues}>{testResult.effectiveGroups.join(', ')}</span></div>
                      )}
                    </div>
                  </>
                )}
              </div>
            </div>
          )}
        </div>

        {/* Footer with Cancel / Apply */}
        <div className={styles.footer}>
          <span className={styles.footerHint}>
            Rules are evaluated top-to-bottom by priority on every OIDC login. If no rules match, the server falls back to roles from the token, then to default roles.
          </span>
          <div className={styles.footerActions}>
            <Button size="sm" variant="ghost" onClick={handleClose}>Cancel</Button>
            <Button size="sm" variant="primary" onClick={handleApply} disabled={!dirty} loading={applying}>Apply</Button>
          </div>
        </div>
      </div>

      <ConfirmDialog
        open={deleteTarget !== null}
        onClose={() => setDeleteTarget(null)}
        onConfirm={confirmDelete}
        message={`Delete this rule? (${deleteTarget?.claim} ${deleteTarget?.matchType} "${deleteTarget?.matchValue}")`}
        confirmText="delete"
      />
    </div>
  );
}
