import { useCallback, useEffect, useState } from 'react';

import {
  disableTrigger,
  enableTrigger,
  listAvailableTriggers,
  listTriggers,
} from '../../lib/composio/composioApi';
import { formatTriggerLabel } from '../../lib/composio/formatters';
import type { ComposioActiveTrigger, ComposioAvailableTrigger } from '../../lib/composio/types';
import { useT } from '../../lib/i18n/I18nContext';
import { useCoreState } from '../../providers/CoreStateProvider';
import { CoreRpcError } from '../../services/coreRpcClient';
import Button from '../ui/Button';

/**
 * Stable signature for matching an `AvailableTrigger` to an
 * `ActiveTrigger`. Static toolkits key by slug; GitHub per-repo
 * triggers key by `slug::owner/repo` to disambiguate the same slug
 * across repos.
 */
export function triggerSignature(
  slug: string,
  scope: 'static' | 'github_repo',
  config?: { owner?: string; repo?: string }
): string {
  if (scope === 'github_repo' && config?.owner && config?.repo) {
    return `${slug.toUpperCase()}::${config.owner.toLowerCase()}/${config.repo.toLowerCase()}`;
  }
  return slug.toUpperCase();
}

export function activeTriggerSignature(t: ComposioActiveTrigger): string {
  const cfg = t.triggerConfig ?? {};
  const owner = typeof cfg.owner === 'string' ? cfg.owner : undefined;
  const repo = typeof cfg.repo === 'string' ? cfg.repo : undefined;
  if (owner && repo) {
    return `${t.slug.toUpperCase()}::${owner.toLowerCase()}/${repo.toLowerCase()}`;
  }
  return t.slug.toUpperCase();
}

export interface TriggerTogglesProps {
  toolkitSlug: string;
  toolkitName: string;
  connectionId: string;
}

export default function TriggerToggles({
  toolkitSlug,
  toolkitName,
  connectionId,
}: TriggerTogglesProps) {
  const { t } = useT();
  const { clearSession } = useCoreState();
  const [available, setAvailable] = useState<ComposioAvailableTrigger[] | null>(null);
  const [activeBySignature, setActiveBySignature] = useState<Map<string, ComposioActiveTrigger>>(
    new Map()
  );
  const [loadError, setLoadError] = useState<string | null>(null);
  // Set when the load failure is a confirmed OpenHuman session expiry (the
  // backend rejected the app-session JWT with a 401 → `SESSION_EXPIRED`).
  // Distinct from a generic load error so we surface an actionable re-auth
  // CTA instead of the raw `SESSION_EXPIRED: …` blob (#4281).
  const [sessionExpired, setSessionExpired] = useState(false);
  const [pendingSignature, setPendingSignature] = useState<string | null>(null);
  const [rowError, setRowError] = useState<string | null>(null);

  // Load both lists in parallel on mount / when connection changes.
  useEffect(() => {
    let cancelled = false;
    setAvailable(null);
    setActiveBySignature(new Map());
    setLoadError(null);
    setSessionExpired(false);
    void (async () => {
      try {
        const [avail, active] = await Promise.all([
          listAvailableTriggers(toolkitSlug, connectionId),
          listTriggers(toolkitSlug),
        ]);
        if (cancelled) return;
        setAvailable(avail.triggers);
        const map = new Map<string, ComposioActiveTrigger>();
        for (const t of active.triggers) {
          if (t.connectionId && t.connectionId !== connectionId) continue;
          map.set(activeTriggerSignature(t), t);
        }
        setActiveBySignature(map);
      } catch (err) {
        if (cancelled) return;
        const msg = err instanceof Error ? err.message : String(err);
        // `auth_expired` is the typed classification of an OpenHuman
        // session-JWT 401 (`SESSION_EXPIRED`). It deliberately excludes
        // downstream provider 401s (`provider_auth`) so a Composio-side
        // auth failure never shows the "sign in again" CTA (#4281, AC#4).
        setSessionExpired(err instanceof CoreRpcError && err.kind === 'auth_expired');
        setLoadError(`${t('composio.triggers.loadError')}: ${msg}`);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [toolkitSlug, connectionId]);

  const handleToggle = useCallback(
    async (entry: ComposioAvailableTrigger) => {
      const config = entry.scope === 'github_repo' ? entry.repo : undefined;
      const sig = triggerSignature(entry.slug, entry.scope, config);
      if (pendingSignature) return;
      setPendingSignature(sig);
      setRowError(null);

      const existing = activeBySignature.get(sig);
      try {
        if (existing) {
          await disableTrigger(existing.id);
          setActiveBySignature(prev => {
            const next = new Map(prev);
            next.delete(sig);
            return next;
          });
        } else {
          const triggerConfig =
            entry.scope === 'github_repo' && entry.repo
              ? { owner: entry.repo.owner, repo: entry.repo.repo }
              : entry.defaultConfig;
          const created = await enableTrigger(connectionId, entry.slug, triggerConfig);
          setActiveBySignature(prev => {
            const next = new Map(prev);
            next.set(sig, {
              id: created.triggerId,
              slug: created.slug,
              toolkit: toolkitSlug,
              connectionId: created.connectionId,
              ...(triggerConfig ? { triggerConfig } : {}),
            });
            return next;
          });
        }
      } catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        const actionWord = existing ? t('common.disable') : t('common.enable');
        setRowError(
          t('triggers.toggleFailed')
            .replace('{action}', actionWord)
            .replace(
              '{trigger}',
              formatTriggerLabel(entry.slug, { toolkit: toolkitName || toolkitSlug })
            )
            .replace('{message}', msg)
        );
      } finally {
        setPendingSignature(null);
      }
    },
    [activeBySignature, connectionId, pendingSignature, toolkitSlug]
  );

  if (loadError) {
    // Session expired → swap the raw `SESSION_EXPIRED: …` blob for an
    // actionable banner whose CTA re-runs the sign-in flow. `clearSession`
    // tears down the stale JWT and flips `isAuthenticated` false, which
    // routes the user to the Welcome / sign-in screen (mirrors the
    // EmbeddingsPanel managed-session pattern). #4281, AC#3.
    if (sessionExpired) {
      return (
        <div
          className="border-t border-line-subtle pt-3 mt-1"
          data-testid="trigger-session-expired">
          <div className="rounded-xl border border-amber-200 dark:border-amber-500/30 bg-amber-50 dark:bg-amber-900/10 p-3">
            <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
              <p className="text-xs text-amber-800 dark:text-amber-200 leading-relaxed">
                {t('composio.triggers.sessionExpired')}
              </p>
              <Button
                type="button"
                variant="secondary"
                size="xs"
                className="shrink-0"
                onClick={() => void clearSession()}>
                {t('settings.embeddings.signInAgain')}
              </Button>
            </div>
          </div>
        </div>
      );
    }
    return (
      <div className="border-t border-line-subtle pt-3 mt-1">
        <p className="text-[11px] text-coral-600">{loadError}</p>
      </div>
    );
  }

  if (available === null) {
    return (
      <div className="border-t border-line-subtle pt-3 mt-1">
        <h3 className="text-xs font-semibold text-content-secondary uppercase tracking-wide">
          {t('composio.triggers.heading')}
        </h3>
        <p className="mt-1 text-[11px] text-content-faint">{t('composio.triggers.loading')}</p>
      </div>
    );
  }

  if (available.length === 0) {
    return (
      <div className="border-t border-line-subtle pt-3 mt-1">
        <h3 className="text-xs font-semibold text-content-secondary uppercase tracking-wide">
          {t('composio.triggers.heading')}
        </h3>
        <p className="mt-1 text-[11px] text-content-faint">
          {`${t('composio.triggers.noneAvailable')} ${toolkitName}.`}
        </p>
      </div>
    );
  }

  return (
    <div className="border-t border-line-subtle pt-3 mt-1 space-y-2" data-testid="trigger-toggles">
      <div className="flex items-baseline justify-between">
        <h3 className="text-xs font-semibold text-content-secondary uppercase tracking-wide">
          {t('composio.triggers.heading')}
        </h3>
        <p className="text-[10px] text-content-faint">{`${t('composio.triggers.listenFrom')} ${toolkitName}`}</p>
      </div>
      <ul className="space-y-1.5 max-h-56 overflow-y-auto pr-1">
        {available.map(entry => {
          const config = entry.scope === 'github_repo' ? entry.repo : undefined;
          const sig = triggerSignature(entry.slug, entry.scope, config);
          const enabled = activeBySignature.has(sig);
          const requiresConfig =
            (entry.requiredConfigKeys?.length ?? 0) > 0 && entry.scope === 'static';
          const isPending = pendingSignature === sig;
          const disabled = requiresConfig || pendingSignature !== null;

          const label =
            entry.scope === 'github_repo' && entry.repo
              ? `${entry.repo.owner}/${entry.repo.repo}`
              : formatTriggerLabel(entry.slug, { toolkit: toolkitName || toolkitSlug });
          const sub =
            entry.scope === 'github_repo'
              ? formatTriggerLabel(entry.slug, { toolkit: toolkitName || toolkitSlug })
              : requiresConfig
                ? t('composio.triggers.needsConfiguration')
                : '';
          const action = enabled ? t('common.disable') : t('common.enable');
          const triggerName = formatTriggerLabel(entry.slug, {
            toolkit: toolkitName || toolkitSlug,
          });
          const ariaLabel =
            entry.scope === 'github_repo' && entry.repo
              ? `${action} ${triggerName} for ${entry.repo.owner}/${entry.repo.repo}`
              : `${action} ${triggerName}`;

          return (
            <li
              key={sig}
              data-testid={`trigger-row-${sig}`}
              className="flex items-start justify-between gap-3 rounded-lg px-2 py-1.5 hover:bg-surface-hover">
              <div className="min-w-0 flex-1">
                <span className="text-sm font-medium text-content break-all">{label}</span>
                {sub && <p className="text-[11px] text-content-faint leading-snug">{sub}</p>}
              </div>
              <button
                type="button"
                role="switch"
                aria-checked={enabled}
                aria-label={ariaLabel}
                disabled={disabled}
                onClick={() => void handleToggle(entry)}
                className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 ${
                  enabled ? 'bg-primary-500' : 'bg-surface-strong'
                }`}>
                <span
                  className={`inline-block h-3.5 w-3.5 transform rounded-full bg-surface shadow transition-transform ${
                    enabled ? 'translate-x-5' : 'translate-x-0.5'
                  } ${isPending ? 'animate-pulse' : ''}`}
                />
              </button>
            </li>
          );
        })}
      </ul>
      {rowError && <p className="text-[11px] text-coral-600">{rowError}</p>}
    </div>
  );
}
