import { useEffect, useState, useCallback, useRef } from 'react';
import {
  Typography,
  Tabs,
  Card,
  Descriptions,
  Tag,
  Space,
  Button,
  Spin,
  message,
  Table,
  Empty,
  Alert,
  Popconfirm,
  Progress,
  Switch,
  Divider,
  Input,
  Modal,
  Form,
} from 'antd';
import {
  ArrowLeftOutlined,
  LinkOutlined,
  ReloadOutlined,
  PlayCircleOutlined,
  PauseCircleOutlined,
  SyncOutlined,
  HeartOutlined,
  CloudDownloadOutlined,
  DeleteOutlined,
  PlusOutlined,
  SettingOutlined,
  LockOutlined,
  EyeOutlined,
  EyeInvisibleOutlined,
  CopyOutlined,
  CloudOutlined,
  DisconnectOutlined,
  UploadOutlined,
  ThunderboltOutlined,
  CloudUploadOutlined,
  BellOutlined,
  CheckCircleOutlined,
  WarningOutlined,
  CloseCircleOutlined,
  InfoCircleOutlined,
  UndoOutlined,
} from '@ant-design/icons';
import dayjs from 'dayjs';
import { useNavigate, useParams } from 'react-router-dom';
import { api } from '@/lib/api';
import type { Instance, HealthCheck, Backup, UpdateStatus, InstanceUpgrade, InstanceEvent } from '@/types/api';
import ServiceHealthGrid, { type ServiceInfo } from '@/components/ServiceHealthGrid';
import LogViewer from '@/components/LogViewer';

const statusColors: Record<string, string> = {
  RUNNING: 'green',
  STOPPED: 'default',
  PROVISIONING: 'processing',
  ERROR: 'red',
  DESTROYING: 'orange',
};

const healthColors: Record<string, string> = {
  HEALTHY: 'green',
  DEGRADED: 'orange',
  UNHEALTHY: 'red',
  UNKNOWN: 'default',
};

export default function InstanceDetailPage() {
  const { id } = useParams<{ id: string }>();
  const navigate = useNavigate();
  const [instance, setInstance] = useState<Instance | null>(null);
  const [loading, setLoading] = useState(true);
  const [services, setServices] = useState<ServiceInfo[]>([]);
  const [servicesLoading, setServicesLoading] = useState(false);
  const [activeTab, setActiveTab] = useState('overview');
  const [actionLoading, setActionLoading] = useState<string | null>(null);
  const [selectedLogService, setSelectedLogService] = useState<string | undefined>(undefined);
  const servicesIntervalRef = useRef<ReturnType<typeof setInterval>>(undefined);

  // Health history
  const [healthHistory, setHealthHistory] = useState<HealthCheck[]>([]);
  const [healthLoading, setHealthLoading] = useState(false);
  const [checkingHealth, setCheckingHealth] = useState(false);

  // Backup state
  const [backups, setBackups] = useState<Backup[]>([]);
  const [backupsLoading, setBackupsLoading] = useState(false);
  const [creatingBackup, setCreatingBackup] = useState(false);

  // Restore state
  const [restoreModal, setRestoreModal] = useState<{ backup: Backup; typedSlug: string } | null>(null);
  const [restoring, setRestoring] = useState(false);
  const [activeRestoreId, setActiveRestoreId] = useState<string | null>(null);
  const [activeRestoreState, setActiveRestoreState] = useState<{
    status: string;
    logTail?: string | null;
    errorMessage?: string | null;
  } | null>(null);

  // Feature reconfiguration state
  const [featureFlags, setFeatureFlags] = useState<Record<string, boolean>>({});
  const [reconfiguring, setReconfiguring] = useState(false);

  // Credentials state
  const [secrets, setSecrets] = useState<Record<string, string | null> | null>(null);
  const [secretsLoading, setSecretsLoading] = useState(false);
  const [secretsError, setSecretsError] = useState<string | null>(null);
  const [secretsFetched, setSecretsFetched] = useState(false);
  const [revealAll, setRevealAll] = useState(false);
  const [passwordVerified, setPasswordVerified] = useState(false);
  const [passwordModalOpen, setPasswordModalOpen] = useState(false);
  const [verifying, setVerifying] = useState(false);
  const [passwordForm] = Form.useForm();

  // Tunnel state
  const [tunnelForm] = Form.useForm();
  const [tunnelSaving, setTunnelSaving] = useState(false);
  const [tunnelRemoving, setTunnelRemoving] = useState(false);

  // Remote tunnel state (Pangolin API managed by CCP)
  const [tunnelStatus, setTunnelStatus] = useState<{
    configured: boolean;
    online?: boolean;
    siteId?: string;
    endpoint?: string;
    resources?: Array<{ subdomain: string; name: string; resourceId: string; hasTarget: boolean; targetIp?: string; targetPort?: number }>;
  } | null>(null);
  const [tunnelStatusLoading, setTunnelStatusLoading] = useState(false);
  const [tunnelSetupRunning, setTunnelSetupRunning] = useState(false);
  const [tunnelSyncing, setTunnelSyncing] = useState(false);
  const [tunnelImporting, setTunnelImporting] = useState(false);

  // Upgrade state
  const [updateStatus, setUpdateStatus] = useState<UpdateStatus | null>(null);
  const [checkingUpdate, setCheckingUpdate] = useState(false);
  const [upgradingInstance, setUpgradingInstance] = useState(false);
  const [currentUpgrade, setCurrentUpgrade] = useState<InstanceUpgrade | null>(null);
  const [upgradeHistory, setUpgradeHistory] = useState<InstanceUpgrade[]>([]);
  const [upgradeHistoryLoading, setUpgradeHistoryLoading] = useState(false);
  const upgradePollingRef = useRef<ReturnType<typeof setInterval>>(undefined);

  // Events state
  const [events, setEvents] = useState<InstanceEvent[]>([]);
  const [eventsLoading, setEventsLoading] = useState(false);
  const [eventsTotal, setEventsTotal] = useState(0);

  const fetchInstance = useCallback(async () => {
    try {
      const { data } = await api.get(`/instances/${id}`);
      setInstance(data.data);
    } catch {
      message.error('Failed to load instance');
      navigate('/app/instances');
    } finally {
      setLoading(false);
    }
  }, [id, navigate]);

  const fetchServices = useCallback(async () => {
    if (!id) return;
    setServicesLoading(true);
    try {
      const { data } = await api.get(`/instances/${id}/services`);
      setServices(data.data);
    } catch {
      // Silently fail — services tab will show empty
    } finally {
      setServicesLoading(false);
    }
  }, [id]);

  const fetchHealthHistory = useCallback(async () => {
    if (!id) return;
    setHealthLoading(true);
    try {
      const { data } = await api.get(`/instances/${id}/health-history`, { params: { limit: 20 } });
      setHealthHistory(data.data);
    } catch {
      // Silently fail
    } finally {
      setHealthLoading(false);
    }
  }, [id]);

  const fetchBackups = useCallback(async () => {
    if (!id) return;
    setBackupsLoading(true);
    try {
      const { data } = await api.get(`/instances/${id}/backups`);
      setBackups(data.data);
    } catch {
      // Silently fail
    } finally {
      setBackupsLoading(false);
    }
  }, [id]);

  const fetchUpgradeStatus = useCallback(async () => {
    if (!id) return;
    try {
      const { data } = await api.get(`/instances/${id}/upgrade-status`);
      setCurrentUpgrade(data.data);
    } catch {
      // Silently fail
    }
  }, [id]);

  const fetchUpgradeHistory = useCallback(async () => {
    if (!id) return;
    setUpgradeHistoryLoading(true);
    try {
      const { data } = await api.get(`/instances/${id}/upgrade-history`, { params: { limit: 20 } });
      setUpgradeHistory(data.data);
    } catch {
      // Silently fail
    } finally {
      setUpgradeHistoryLoading(false);
    }
  }, [id]);

  const fetchEvents = useCallback(async () => {
    if (!id) return;
    setEventsLoading(true);
    try {
      const { data } = await api.get(`/instances/${id}/events`, { params: { limit: 50 } });
      setEvents(data.data);
      setEventsTotal(data.total);
    } catch {
      // Silently fail
    } finally {
      setEventsLoading(false);
    }
  }, [id]);

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

  // Auto-refresh services every 10s when on the services tab
  useEffect(() => {
    if (activeTab === 'services') {
      fetchServices();
      servicesIntervalRef.current = setInterval(fetchServices, 10_000);
    }
    return () => {
      if (servicesIntervalRef.current) clearInterval(servicesIntervalRef.current);
    };
  }, [activeTab, fetchServices]);

  // Fetch health history when on overview tab
  useEffect(() => {
    if (activeTab === 'overview' && instance?.status === 'RUNNING') {
      fetchHealthHistory();
    }
  }, [activeTab, instance?.status, fetchHealthHistory]);

  // Fetch backups when on backups tab
  useEffect(() => {
    if (activeTab === 'backups') {
      fetchBackups();
    }
  }, [activeTab, fetchBackups]);

  // Fetch upgrade status/history when on updates tab
  useEffect(() => {
    if (activeTab === 'updates') {
      fetchUpgradeStatus();
      fetchUpgradeHistory();
    }
  }, [activeTab, fetchUpgradeStatus, fetchUpgradeHistory]);

  // Poll upgrade progress when an upgrade is in progress
  useEffect(() => {
    if (currentUpgrade?.status === 'IN_PROGRESS' || currentUpgrade?.status === 'PENDING') {
      upgradePollingRef.current = setInterval(fetchUpgradeStatus, 2_000);
    }
    return () => {
      if (upgradePollingRef.current) clearInterval(upgradePollingRef.current);
    };
  }, [currentUpgrade?.status, fetchUpgradeStatus]);

  // Fetch events when on events tab
  useEffect(() => {
    if (activeTab === 'events') {
      fetchEvents();
    }
  }, [activeTab, fetchEvents]);

  // Fetch secrets (only called after password verification)
  const fetchSecrets = useCallback(async () => {
    if (!id) return;
    setSecretsLoading(true);
    setSecretsError(null);
    try {
      const { data } = await api.get(`/instances/${id}/secrets`);
      setSecrets(data.data);
      setSecretsFetched(true);
    } catch (err: unknown) {
      const resp = (err as { response?: { data?: { error?: { message?: string } } } })?.response
        ?.data?.error;
      setSecretsError(resp?.message || 'Failed to load credentials');
    } finally {
      setSecretsLoading(false);
    }
  }, [id]);

  // Show password modal when credentials tab is first activated
  useEffect(() => {
    if (activeTab === 'credentials' && !passwordVerified && !secretsFetched) {
      setPasswordModalOpen(true);
    }
  }, [activeTab, passwordVerified, secretsFetched]);

  const handlePasswordVerify = async (values: { password: string }) => {
    setVerifying(true);
    try {
      await api.post('/auth/verify-password', { password: values.password });
      setPasswordVerified(true);
      setPasswordModalOpen(false);
      passwordForm.resetFields();
      fetchSecrets();
    } catch {
      message.error('Incorrect password');
      passwordForm.setFields([{ name: 'password', errors: ['Incorrect password'] }]);
    } finally {
      setVerifying(false);
    }
  };

  // Auto-refresh instance when in PROVISIONING state
  useEffect(() => {
    if (instance?.status === 'PROVISIONING') {
      const interval = setInterval(fetchInstance, 3_000);
      return () => clearInterval(interval);
    }
  }, [instance?.status, fetchInstance]);

  // Fetch tunnel status for remote instances (must be before early return)
  const fetchTunnelStatus = useCallback(async () => {
    if (!instance?.isRemote) return;
    setTunnelStatusLoading(true);
    try {
      const { data } = await api.get(`/instances/${id}/tunnel/status`);
      setTunnelStatus(data.data);
    } catch {
      setTunnelStatus(null);
    } finally {
      setTunnelStatusLoading(false);
    }
  }, [id, instance?.isRemote]);

  useEffect(() => {
    if (activeTab === 'tunnel' && instance?.isRemote) {
      fetchTunnelStatus();
    }
  }, [activeTab, instance?.isRemote, fetchTunnelStatus]);

  const handleAction = async (action: string, label: string) => {
    setActionLoading(action);
    try {
      await api.post(`/instances/${id}/${action}`);
      message.success(`${label} successful`);
      fetchInstance();
      if (activeTab === 'services') fetchServices();
    } catch (err: unknown) {
      const resp = (err as { response?: { data?: { error?: { message?: string } } } })?.response
        ?.data?.error;
      message.error(resp?.message || `${label} failed`);
    } finally {
      setActionLoading(null);
    }
  };

  const handleServiceRestart = async (service: string) => {
    try {
      await api.post(`/instances/${id}/restart?service=${service}`);
      message.success(`${service} restarted`);
      fetchServices();
    } catch {
      message.error(`Failed to restart ${service}`);
    }
  };

  const handleViewServiceLogs = (service: string) => {
    setSelectedLogService(service);
    setActiveTab('logs');
  };

  const handleHealthCheck = async () => {
    setCheckingHealth(true);
    try {
      await api.post(`/instances/${id}/health-check`);
      message.success('Health check complete');
      fetchHealthHistory();
      fetchInstance();
    } catch (err: unknown) {
      const resp = (err as { response?: { data?: { error?: { message?: string } } } })?.response
        ?.data?.error;
      message.error(resp?.message || 'Health check failed');
    } finally {
      setCheckingHealth(false);
    }
  };

  const handleCreateBackup = async () => {
    setCreatingBackup(true);
    try {
      await api.post(`/instances/${id}/backup`);
      message.success('Backup started');
      fetchBackups();
    } catch (err: unknown) {
      const resp = (err as { response?: { data?: { error?: { message?: string } } } })?.response
        ?.data?.error;
      message.error(resp?.message || 'Failed to create backup');
    } finally {
      setCreatingBackup(false);
    }
  };

  const handleDeleteBackup = async (backupId: string) => {
    try {
      await api.delete(`/backups/${backupId}`);
      message.success('Backup deleted');
      fetchBackups();
    } catch {
      message.error('Failed to delete backup');
    }
  };

  const handleDownloadBackup = (backupId: string) => {
    window.open(`/api/backups/${backupId}/download`, '_blank');
  };

  const handleRestoreConfirm = async () => {
    if (!restoreModal) return;
    if (restoreModal.typedSlug !== instance?.slug) {
      message.error('Typed slug does not match — restore cancelled');
      return;
    }
    setRestoring(true);
    try {
      const { data } = await api.post(`/instances/${id}/restore`, {
        backupId: restoreModal.backup.id,
      });
      const restoreId = data.data.id as string;
      setActiveRestoreId(restoreId);
      setActiveRestoreState({ status: 'PENDING' });
      setRestoreModal(null);
      message.success('Restore started — polling for progress');
    } catch (err: unknown) {
      const e = err as { response?: { data?: { error?: { message?: string } } } };
      message.error(e?.response?.data?.error?.message || 'Failed to start restore');
    } finally {
      setRestoring(false);
    }
  };

  // Poll the active restore's status every 3s until it completes or fails
  useEffect(() => {
    if (!activeRestoreId) return;
    let cancelled = false;
    const poll = async () => {
      try {
        const { data } = await api.get(`/instances/${id}/restores/${activeRestoreId}`);
        if (cancelled) return;
        const row = data.data;
        setActiveRestoreState({
          status: row.status,
          logTail: row.logTail,
          errorMessage: row.errorMessage,
        });
        if (row.status === 'COMPLETED') {
          message.success('Restore completed successfully');
          setActiveRestoreId(null);
          fetchBackups();
        } else if (row.status === 'FAILED') {
          message.error(`Restore failed: ${row.errorMessage || 'unknown error'}`);
          setActiveRestoreId(null);
        }
      } catch {
        // keep trying; transient errors are expected during remote restart
      }
    };
    poll();
    const handle = setInterval(poll, 3000);
    return () => {
      cancelled = true;
      clearInterval(handle);
    };
  }, [activeRestoreId, id, fetchBackups]);

  // Initialize feature flags and tunnel form when instance loads
  useEffect(() => {
    if (instance) {
      setFeatureFlags({
        enableMedia: instance.enableMedia,
        enableListmonk: instance.enableListmonk,
        enableGancio: instance.enableGancio,
        enableChat: instance.enableChat,
        enableMonitoring: instance.enableMonitoring,
        enableDevTools: instance.enableDevTools,
        enablePayments: instance.enablePayments,
        enableMeet: instance.enableMeet,
        enableSms: instance.enableSms,
        enableSocial: instance.enableSocial,
        enablePeople: instance.enablePeople,
        enableAnalytics: instance.enableAnalytics,
      });
      tunnelForm.setFieldsValue({
        pangolinEndpoint: instance.pangolinEndpoint || '',
        pangolinNewtId: instance.pangolinNewtId || '',
        pangolinNewtSecret: '',
      });
    }
  }, [instance, tunnelForm]);

  const handleReconfigure = async () => {
    setReconfiguring(true);
    try {
      await api.post(`/instances/${id}/reconfigure`, featureFlags);
      message.success('Reconfiguration complete');
      fetchInstance();
      if (activeTab === 'services') fetchServices();
    } catch (err: unknown) {
      const resp = (err as { response?: { data?: { error?: { message?: string } } } })?.response
        ?.data?.error;
      message.error(resp?.message || 'Reconfiguration failed');
    } finally {
      setReconfiguring(false);
    }
  };

  // Upgrade handlers
  const handleCheckUpdate = async () => {
    setCheckingUpdate(true);
    try {
      const { data } = await api.post(`/instances/${id}/check-update`);
      setUpdateStatus(data.data);
    } catch (err: unknown) {
      const resp = (err as { response?: { data?: { error?: { message?: string } } } })?.response
        ?.data?.error;
      message.error(resp?.message || 'Failed to check for updates');
    } finally {
      setCheckingUpdate(false);
    }
  };

  const handleStartUpgrade = async () => {
    setUpgradingInstance(true);
    try {
      const { data } = await api.post(`/instances/${id}/upgrade`, { skipBackup: true });
      setCurrentUpgrade(data.data);
      message.success('Upgrade started');
    } catch (err: unknown) {
      const resp = (err as { response?: { data?: { error?: { message?: string } } } })?.response
        ?.data?.error;
      message.error(resp?.message || 'Failed to start upgrade');
    } finally {
      setUpgradingInstance(false);
    }
  };

  // Image-only upgrade (Approach B): pulls images + recreates core app services
  // without touching tracked files. Faster + safer than full upgrade for releases
  // that don't change compose/templates.
  const handleStartImageUpgrade = async () => {
    setUpgradingInstance(true);
    try {
      const { data } = await api.post(`/instances/${id}/upgrade-images`, {});
      setCurrentUpgrade(data.data);
      message.success('Image-only upgrade started');
    } catch (err: unknown) {
      const resp = (err as { response?: { data?: { error?: { message?: string } } } })?.response
        ?.data?.error;
      message.error(resp?.message || 'Failed to start image-only upgrade');
    } finally {
      setUpgradingInstance(false);
    }
  };

  // Release upgrade (Approach C): CCP re-renders templates with new image tag,
  // writes them to the tenant, then composePull + composeUp. For releases
  // that change orchestration (new services, compose config) in addition
  // to image versions. Tenant content (mkdocs/, customized configs/) is
  // never touched.
  const [releaseUpgradeModalOpen, setReleaseUpgradeModalOpen] = useState(false);
  const [releaseImageTag, setReleaseImageTag] = useState<string>('');
  const [releasePreview, setReleasePreview] = useState<{
    files: Array<{ path: string; status: string; diff: string | null; sizeBefore: number; sizeAfter: number }>;
    envCoverage?: { requiredVars: string[]; presentInTenantEnv: string[]; missingInTenantEnv: string[] };
  } | null>(null);
  const [releasePreviewLoading, setReleasePreviewLoading] = useState(false);

  const handlePreviewReleaseUpgrade = async () => {
    setReleasePreviewLoading(true);
    setReleasePreview(null);
    try {
      const body = releaseImageTag.trim() ? { imageTag: releaseImageTag.trim() } : {};
      const { data } = await api.post(`/instances/${id}/upgrade-release/preview`, body);
      setReleasePreview(data.data);
    } catch (err: unknown) {
      const resp = (err as { response?: { data?: { error?: { message?: string } } } })?.response
        ?.data?.error;
      message.error(resp?.message || 'Preview failed');
    } finally {
      setReleasePreviewLoading(false);
    }
  };

  const handleStartReleaseUpgrade = async () => {
    setUpgradingInstance(true);
    try {
      const body = releaseImageTag.trim() ? { imageTag: releaseImageTag.trim() } : {};
      const { data } = await api.post(`/instances/${id}/upgrade-release`, body);
      setCurrentUpgrade(data.data);
      setReleaseUpgradeModalOpen(false);
      setReleasePreview(null);
      message.success('Release upgrade started');
    } catch (err: unknown) {
      const resp = (err as { response?: { data?: { error?: { message?: string } } } })?.response
        ?.data?.error;
      message.error(resp?.message || 'Failed to start release upgrade');
    } finally {
      setUpgradingInstance(false);
    }
  };

  // Event handlers
  const handleAcknowledgeEvent = async (eventId: string) => {
    try {
      await api.put(`/events/${eventId}/acknowledge`);
      message.success('Event acknowledged');
      fetchEvents();
    } catch {
      message.error('Failed to acknowledge event');
    }
  };

  const handleAcknowledgeAll = async () => {
    try {
      await api.put(`/instances/${id}/events/acknowledge-all`);
      message.success('All events acknowledged');
      fetchEvents();
    } catch {
      message.error('Failed to acknowledge events');
    }
  };

  const hasFeatureChanges = instance ? (
    featureFlags.enableMedia !== instance.enableMedia ||
    featureFlags.enableListmonk !== instance.enableListmonk ||
    featureFlags.enableGancio !== instance.enableGancio ||
    featureFlags.enableChat !== instance.enableChat ||
    featureFlags.enableMonitoring !== instance.enableMonitoring ||
    featureFlags.enableDevTools !== instance.enableDevTools ||
    featureFlags.enablePayments !== instance.enablePayments ||
    featureFlags.enableMeet !== instance.enableMeet ||
    featureFlags.enableSms !== instance.enableSms ||
    featureFlags.enableSocial !== instance.enableSocial ||
    featureFlags.enablePeople !== instance.enablePeople ||
    featureFlags.enableAnalytics !== instance.enableAnalytics
  ) : false;

  if (loading || !instance) {
    return (
      <div style={{ display: 'flex', justifyContent: 'center', padding: 100 }}>
        <Spin size="large" />
      </div>
    );
  }

  const ports = instance.portConfig as Record<string, number>;
  const isProvisioning = instance.status === 'PROVISIONING';
  const isRegistered = instance.isRegistered;
  const isRemote = instance.isRemote;
  // A "managed" instance is one CCP can run backup/restore/upgrade on.
  // Local CCP-managed and remote (agent-backed) both qualify; only locally-
  // adopted registered instances (isRegistered && !isRemote) are unmanaged.
  const isManaged = !isRegistered || isRemote;
  const canStart = instance.status === 'STOPPED' || instance.status === 'ERROR';
  const canStop = instance.status === 'RUNNING' || instance.status === 'ERROR';
  const canRestart = instance.status === 'RUNNING';
  const canProvision = !isRegistered && (instance.status === 'ERROR' || instance.status === 'STOPPED');

  const latestHealth = instance.healthChecks?.[0];

  const overviewTab = (
    <Space direction="vertical" size="large" style={{ width: '100%' }}>
      {isProvisioning && (
        <Alert
          message="Provisioning in Progress"
          description={instance.statusMessage || 'Setting up instance...'}
          type="info"
          showIcon
          icon={<SyncOutlined spin />}
        />
      )}

      <Descriptions bordered column={{ xs: 1, sm: 2 }}>
        <Descriptions.Item label="Name">{instance.name}</Descriptions.Item>
        <Descriptions.Item label="Status">
          <Tag color={statusColors[instance.status]}>{instance.status}</Tag>
        </Descriptions.Item>
        <Descriptions.Item label="Deployment">
          {isRegistered ? (
            <Tag color="purple">External — deployed outside CCP</Tag>
          ) : (
            <Tag color="blue">Managed — provisioned by CCP</Tag>
          )}
        </Descriptions.Item>
        <Descriptions.Item label="Domain">{instance.domain}</Descriptions.Item>
        <Descriptions.Item label="Slug">{instance.slug}</Descriptions.Item>
        <Descriptions.Item label="Compose Project">{instance.composeProject}</Descriptions.Item>
        <Descriptions.Item label="Git Branch">{instance.gitBranch}</Descriptions.Item>
        <Descriptions.Item label="Admin Email">{instance.adminEmail}</Descriptions.Item>
        <Descriptions.Item label="Email Mode">
          {instance.emailTestMode ? 'Test (MailHog)' : `SMTP: ${instance.smtpHost}:${instance.smtpPort}`}
        </Descriptions.Item>
        <Descriptions.Item label="Base Path" span={2}>
          <Typography.Text code>{instance.basePath}</Typography.Text>
        </Descriptions.Item>
        <Descriptions.Item label="Created">{new Date(instance.createdAt).toLocaleString()}</Descriptions.Item>
        <Descriptions.Item label="Updated">{new Date(instance.updatedAt).toLocaleString()}</Descriptions.Item>
      </Descriptions>

      <Card title="Features" size="small">
        <Space wrap>
          <Tag color={instance.enableMedia ? 'blue' : 'default'}>
            Media {instance.enableMedia ? 'ON' : 'OFF'}
          </Tag>
          <Tag color={instance.enableListmonk ? 'green' : 'default'}>
            Newsletter {instance.enableListmonk ? 'ON' : 'OFF'}
          </Tag>
          <Tag color={instance.enableGancio ? 'purple' : 'default'}>
            Events {instance.enableGancio ? 'ON' : 'OFF'}
          </Tag>
          <Tag color={instance.enableChat ? 'orange' : 'default'}>
            Chat {instance.enableChat ? 'ON' : 'OFF'}
          </Tag>
          <Tag color={instance.enableMonitoring ? 'cyan' : 'default'}>
            Monitoring {instance.enableMonitoring ? 'ON' : 'OFF'}
          </Tag>
          <Tag color={instance.enableDevTools ? 'geekblue' : 'default'}>
            Dev Tools {instance.enableDevTools ? 'ON' : 'OFF'}
          </Tag>
          <Tag color={instance.enablePayments ? 'gold' : 'default'}>
            Payments {instance.enablePayments ? 'ON' : 'OFF'}
          </Tag>
          <Tag color={instance.enableMeet ? 'volcano' : 'default'}>
            Jitsi Meet {instance.enableMeet ? 'ON' : 'OFF'}
          </Tag>
          <Tag color={instance.enableSms ? 'lime' : 'default'}>
            SMS {instance.enableSms ? 'ON' : 'OFF'}
          </Tag>
          <Tag color={instance.enableSocial ? 'magenta' : 'default'}>
            Social {instance.enableSocial ? 'ON' : 'OFF'}
          </Tag>
          <Tag color={instance.enablePeople ? 'red' : 'default'}>
            People CRM {instance.enablePeople ? 'ON' : 'OFF'}
          </Tag>
          <Tag color={instance.enableAnalytics ? 'geekblue' : 'default'}>
            Analytics {instance.enableAnalytics ? 'ON' : 'OFF'}
          </Tag>
        </Space>
      </Card>

      <Card title="Port Allocations" size="small">
        <Descriptions column={{ xs: 1, sm: 2 }}>
          <Descriptions.Item label="API">{ports.api}</Descriptions.Item>
          <Descriptions.Item label="Admin">{ports.admin}</Descriptions.Item>
          <Descriptions.Item label="PostgreSQL">{ports.postgres}</Descriptions.Item>
          <Descriptions.Item label="Nginx">{ports.nginx}</Descriptions.Item>
        </Descriptions>
      </Card>

      {/* Health History Section */}
      {instance.status === 'RUNNING' && (
        <Card
          title={
            <Space>
              <HeartOutlined />
              <span>Health</span>
              {latestHealth && (
                <Tag color={healthColors[latestHealth.status]}>{latestHealth.status}</Tag>
              )}
            </Space>
          }
          size="small"
          extra={
            <Button
              icon={<HeartOutlined />}
              size="small"
              onClick={handleHealthCheck}
              loading={checkingHealth}
            >
              Check Now
            </Button>
          }
        >
          {latestHealth && (
            <div style={{ marginBottom: 16 }}>
              <Typography.Text type="secondary" style={{ fontSize: 12 }}>
                Services: {latestHealth.healthyServices}/{latestHealth.totalServices}
              </Typography.Text>
              <Progress
                percent={Math.round((latestHealth.healthyServices / latestHealth.totalServices) * 100)}
                size="small"
                status={
                  latestHealth.status === 'HEALTHY' ? 'success'
                    : latestHealth.status === 'DEGRADED' ? 'active'
                    : 'exception'
                }
              />
              {latestHealth.responseTimeMs != null && (
                <Typography.Text type="secondary" style={{ fontSize: 12 }}>
                  Response: {latestHealth.responseTimeMs}ms
                </Typography.Text>
              )}
            </div>
          )}

          <Table
            dataSource={healthHistory}
            rowKey="id"
            loading={healthLoading}
            size="small"
            pagination={false}
            columns={[
              {
                title: 'Time',
                dataIndex: 'checkedAt',
                width: 160,
                render: (d: string) => dayjs(d).format('MM-DD HH:mm:ss'),
              },
              {
                title: 'Status',
                dataIndex: 'status',
                width: 100,
                render: (s: string) => <Tag color={healthColors[s]}>{s}</Tag>,
              },
              {
                title: 'Services',
                render: (_: unknown, r: HealthCheck) => `${r.healthyServices}/${r.totalServices}`,
                width: 80,
              },
              {
                title: 'Response',
                dataIndex: 'responseTimeMs',
                width: 80,
                render: (ms: number | null) => (ms != null ? `${ms}ms` : '-'),
              },
            ]}
          />
        </Card>
      )}

      {instance.statusMessage && instance.status === 'ERROR' && (
        <Alert
          message="Error"
          description={instance.statusMessage}
          type="error"
          showIcon
          action={
            canProvision ? (
              <Button
                size="small"
                onClick={() => handleAction('provision', 'Retry provisioning')}
                loading={actionLoading === 'provision'}
              >
                Retry
              </Button>
            ) : undefined
          }
        />
      )}
    </Space>
  );

  const serviceNames = services.map((s) => s.service).filter(Boolean);

  const servicesTab = (
    <div>
      <div style={{ marginBottom: 12, display: 'flex', justifyContent: 'flex-end' }}>
        <Button icon={<ReloadOutlined />} onClick={fetchServices} loading={servicesLoading}>
          Refresh
        </Button>
      </div>
      <ServiceHealthGrid
        services={services}
        loading={servicesLoading}
        onRestart={handleServiceRestart}
        onViewLogs={handleViewServiceLogs}
      />
    </div>
  );

  const logsTab = (
    <LogViewer instanceId={id!} services={serviceNames} initialService={selectedLogService} />
  );

  const backupsTab = (
    <div>
      {!isManaged && (
        <Alert
          message="Backups not managed by CCP"
          description="This instance was deployed outside the control panel. Use its own backup tools to manage backups."
          type="info"
          showIcon
          style={{ marginBottom: 16 }}
        />
      )}
      {isRemote && (
        <Alert
          message="Remote instance"
          description="Backups and restores run via the remote agent over mTLS. Create Backup triggers scripts/backup.sh on the remote host and streams the archive back to the control panel."
          type="info"
          showIcon
          style={{ marginBottom: 16 }}
        />
      )}
      <div style={{ marginBottom: 12, display: 'flex', justifyContent: 'space-between' }}>
        <Typography.Text type="secondary">
          {backups.length} backup{backups.length !== 1 ? 's' : ''}
        </Typography.Text>
        <Button
          icon={<PlusOutlined />}
          type="primary"
          onClick={handleCreateBackup}
          loading={creatingBackup}
          disabled={instance.status !== 'RUNNING' || !isManaged}
        >
          Create Backup
        </Button>
      </div>
      {backups.length > 0 ? (
        <Table
          dataSource={backups}
          rowKey="id"
          loading={backupsLoading}
          size="small"
          columns={[
            {
              title: 'Status',
              dataIndex: 'status',
              width: 120,
              render: (s: string) => (
                <Tag color={s === 'COMPLETED' ? 'green' : s === 'FAILED' ? 'red' : s === 'IN_PROGRESS' ? 'processing' : 'default'}>
                  {s}
                </Tag>
              ),
            },
            {
              title: 'Started',
              dataIndex: 'startedAt',
              render: (d: string) => dayjs(d).format('YYYY-MM-DD HH:mm'),
            },
            {
              title: 'Completed',
              dataIndex: 'completedAt',
              render: (d: string | null) => (d ? dayjs(d).format('YYYY-MM-DD HH:mm') : '-'),
            },
            {
              title: 'Size',
              dataIndex: 'sizeBytes',
              render: (b: number | string | null) => {
                if (b == null) return '-';
                const n = typeof b === 'string' ? parseInt(b, 10) : b;
                return `${(n / 1024 / 1024).toFixed(1)} MB`;
              },
            },
            {
              title: 'Actions',
              width: 160,
              render: (_: unknown, record: Backup) => (
                <Space size="small">
                  {record.status === 'COMPLETED' && (
                    <>
                      <Button
                        icon={<CloudDownloadOutlined />}
                        size="small"
                        type="text"
                        title="Download archive"
                        onClick={() => handleDownloadBackup(record.id)}
                      />
                      {isManaged && (
                        <Button
                          icon={<UndoOutlined />}
                          size="small"
                          type="text"
                          title="Restore this backup (destructive)"
                          onClick={() => setRestoreModal({ backup: record, typedSlug: '' })}
                        />
                      )}
                    </>
                  )}
                  <Popconfirm
                    title="Delete this backup?"
                    onConfirm={() => handleDeleteBackup(record.id)}
                  >
                    <Button icon={<DeleteOutlined />} size="small" type="text" danger />
                  </Popconfirm>
                </Space>
              ),
            },
          ]}
        />
      ) : (
        <Empty description="No backups yet" />
      )}
    </div>
  );

  const canReconfigure = !isRegistered && (instance.status === 'RUNNING' || instance.status === 'STOPPED');

  const featuresTab = (
    <Space direction="vertical" size="large" style={{ width: '100%' }}>
      {isRegistered && (
        <Alert
          message="Features are read-only for external instances"
          description="This instance was deployed outside the control panel. Feature toggles are synced automatically from the instance's .env file during health checks."
          type="info"
          showIcon
        />
      )}
      {!isRegistered && (
        <Alert
          message="Reconfiguration will regenerate config files"
          description="Applying changes will re-render docker-compose.yml, .env, and nginx configs, then restart Docker services. This may cause brief downtime."
          type="warning"
          showIcon
        />
      )}

      <Card title="Application Features" size="small">
        <Space direction="vertical" style={{ width: '100%' }} size="middle">
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div>
              <Typography.Text strong>Media Manager</Typography.Text>
              <br />
              <Typography.Text type="secondary">Video library, uploads, gallery, analytics</Typography.Text>
            </div>
            <Switch
              checked={featureFlags.enableMedia}
              onChange={(v) => setFeatureFlags((f) => ({ ...f, enableMedia: v }))}
              disabled={isRegistered}
            />
          </div>
          <Divider style={{ margin: '4px 0' }} />
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div>
              <Typography.Text strong>Payments (Stripe)</Typography.Text>
              <br />
              <Typography.Text type="secondary">Products, donations, subscriptions, ticketed events</Typography.Text>
            </div>
            <Switch
              checked={featureFlags.enablePayments}
              onChange={(v) => setFeatureFlags((f) => ({ ...f, enablePayments: v }))}
              disabled={isRegistered}
            />
          </div>
          <Divider style={{ margin: '4px 0' }} />
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div>
              <Typography.Text strong>Newsletter (Listmonk)</Typography.Text>
              <br />
              <Typography.Text type="secondary">Email newsletters, subscriber management</Typography.Text>
            </div>
            <Switch
              checked={featureFlags.enableListmonk}
              onChange={(v) => setFeatureFlags((f) => ({ ...f, enableListmonk: v }))}
              disabled={isRegistered}
            />
          </div>
          <Divider style={{ margin: '4px 0' }} />
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div>
              <Typography.Text strong>Events (Gancio)</Typography.Text>
              <br />
              <Typography.Text type="secondary">Community events calendar, shift sync</Typography.Text>
            </div>
            <Switch
              checked={featureFlags.enableGancio}
              onChange={(v) => setFeatureFlags((f) => ({ ...f, enableGancio: v }))}
              disabled={isRegistered}
            />
          </div>
          <Divider style={{ margin: '4px 0' }} />
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div>
              <Typography.Text strong>People CRM</Typography.Text>
              <br />
              <Typography.Text type="secondary">Unified people management, contact linking</Typography.Text>
            </div>
            <Switch
              checked={featureFlags.enablePeople}
              onChange={(v) => setFeatureFlags((f) => ({ ...f, enablePeople: v }))}
              disabled={isRegistered}
            />
          </div>
          <Divider style={{ margin: '4px 0' }} />
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div>
              <Typography.Text strong>Analytics & GeoIP</Typography.Text>
              <br />
              <Typography.Text type="secondary">Visitor geography tracking, user drill-down, unified dashboard</Typography.Text>
            </div>
            <Switch
              checked={featureFlags.enableAnalytics}
              onChange={(v) => setFeatureFlags((f) => ({ ...f, enableAnalytics: v }))}
              disabled={isRegistered}
            />
          </div>
        </Space>
      </Card>

      <Card title="Communication" size="small">
        <Space direction="vertical" style={{ width: '100%' }} size="middle">
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div>
              <Typography.Text strong>Chat (Rocket.Chat)</Typography.Text>
              <br />
              <Typography.Text type="secondary">Team communication, channels</Typography.Text>
            </div>
            <Switch
              checked={featureFlags.enableChat}
              onChange={(v) => setFeatureFlags((f) => ({ ...f, enableChat: v }))}
              disabled={isRegistered}
            />
          </div>
          <Divider style={{ margin: '4px 0' }} />
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div>
              <Typography.Text strong>Video Conferencing (Jitsi Meet)</Typography.Text>
              <br />
              <Typography.Text type="secondary">Self-hosted video calls (4 containers)</Typography.Text>
            </div>
            <Switch
              checked={featureFlags.enableMeet}
              onChange={(v) => setFeatureFlags((f) => ({ ...f, enableMeet: v }))}
              disabled={isRegistered}
            />
          </div>
          <Divider style={{ margin: '4px 0' }} />
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div>
              <Typography.Text strong>SMS Campaigns</Typography.Text>
              <br />
              <Typography.Text type="secondary">Termux Android bridge, bulk SMS outreach</Typography.Text>
            </div>
            <Switch
              checked={featureFlags.enableSms}
              onChange={(v) => setFeatureFlags((f) => ({ ...f, enableSms: v }))}
              disabled={isRegistered}
            />
          </div>
          <Divider style={{ margin: '4px 0' }} />
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div>
              <Typography.Text strong>Social Connections</Typography.Text>
              <br />
              <Typography.Text type="secondary">Volunteer friendships, challenges, spotlights, referrals</Typography.Text>
            </div>
            <Switch
              checked={featureFlags.enableSocial}
              onChange={(v) => setFeatureFlags((f) => ({ ...f, enableSocial: v }))}
              disabled={isRegistered}
            />
          </div>
        </Space>
      </Card>

      <Card title="Infrastructure" size="small">
        <Space direction="vertical" style={{ width: '100%' }} size="middle">
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div>
              <Typography.Text strong>Monitoring</Typography.Text>
              <br />
              <Typography.Text type="secondary">Prometheus, Grafana, Alertmanager, cAdvisor</Typography.Text>
            </div>
            <Switch
              checked={featureFlags.enableMonitoring}
              onChange={(v) => setFeatureFlags((f) => ({ ...f, enableMonitoring: v }))}
              disabled={isRegistered}
            />
          </div>
          <Divider style={{ margin: '4px 0' }} />
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div>
              <Typography.Text strong>Dev Tools</Typography.Text>
              <br />
              <Typography.Text type="secondary">Code Server, Gitea, n8n, Homepage, Excalidraw</Typography.Text>
            </div>
            <Switch
              checked={featureFlags.enableDevTools}
              onChange={(v) => setFeatureFlags((f) => ({ ...f, enableDevTools: v }))}
              disabled={isRegistered}
            />
          </div>
        </Space>
      </Card>

      {canReconfigure && (
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12 }}>
          <Button
            onClick={() => {
              setFeatureFlags({
                enableMedia: instance.enableMedia,
                enableListmonk: instance.enableListmonk,
                enableGancio: instance.enableGancio,
                enableChat: instance.enableChat,
                enableMonitoring: instance.enableMonitoring,
                enableDevTools: instance.enableDevTools,
                enablePayments: instance.enablePayments,
                enableMeet: instance.enableMeet,
                enableSms: instance.enableSms,
                enableSocial: instance.enableSocial,
                enablePeople: instance.enablePeople,
                enableAnalytics: instance.enableAnalytics,
              });
            }}
            disabled={!hasFeatureChanges}
          >
            Reset
          </Button>
          <Popconfirm
            title="Apply feature changes?"
            description="This will regenerate configs and restart affected services."
            onConfirm={handleReconfigure}
            disabled={!hasFeatureChanges}
          >
            <Button
              type="primary"
              icon={<SettingOutlined />}
              loading={reconfiguring}
              disabled={!hasFeatureChanges}
            >
              Apply Changes
            </Button>
          </Popconfirm>
        </div>
      )}
    </Space>
  );

  const tunnelConfigured = !!(instance.pangolinEndpoint && instance.pangolinNewtId);
  const canConfigureTunnel = isManaged && (instance.status === 'RUNNING' || instance.status === 'STOPPED');

  const handleRemoteTunnelSetup = async (values: { subdomainPrefix?: string }) => {
    setTunnelSetupRunning(true);
    try {
      await api.post(`/instances/${id}/tunnel/setup`, {
        subdomainPrefix: values.subdomainPrefix || '',
      });
      message.success('Tunnel setup complete — Newt credentials pushed to remote instance');
      fetchInstance();
      fetchTunnelStatus();
    } catch (err: unknown) {
      const e = err as { response?: { data?: { error?: { message?: string } } } };
      message.error(e?.response?.data?.error?.message || 'Tunnel setup failed');
    } finally {
      setTunnelSetupRunning(false);
    }
  };

  const handleTunnelImport = async () => {
    setTunnelImporting(true);
    try {
      const { data } = await api.post(`/instances/${id}/tunnel/import`);
      message.success(
        `Tunnel imported — site ${data.data.siteId} (${data.data.online ? 'online' : 'offline'})`
      );
      fetchInstance();
      fetchTunnelStatus();
    } catch (err: unknown) {
      const e = err as { response?: { data?: { error?: { message?: string } } } };
      message.error(e?.response?.data?.error?.message || 'Import failed');
    } finally {
      setTunnelImporting(false);
    }
  };

  const handleTunnelSync = async () => {
    setTunnelSyncing(true);
    try {
      const { data } = await api.post(`/instances/${id}/tunnel/sync`);
      message.success(`Sync complete — ${data.data.created} new resource(s) created`);
      fetchTunnelStatus();
    } catch (err: unknown) {
      const e = err as { response?: { data?: { error?: { message?: string } } } };
      message.error(e?.response?.data?.error?.message || 'Sync failed');
    } finally {
      setTunnelSyncing(false);
    }
  };

  const handleRemoteTunnelTeardown = async () => {
    setTunnelRemoving(true);
    try {
      await api.delete(`/instances/${id}/tunnel`);
      message.success('Tunnel torn down — Pangolin site deleted');
      fetchInstance();
      setTunnelStatus(null);
    } catch (err: unknown) {
      const e = err as { response?: { data?: { error?: { message?: string } } } };
      message.error(e?.response?.data?.error?.message || 'Teardown failed');
    } finally {
      setTunnelRemoving(false);
    }
  };

  const handleConfigureTunnel = async (values: { pangolinEndpoint: string; pangolinNewtId: string; pangolinNewtSecret?: string }) => {
    setTunnelSaving(true);
    try {
      const payload: Record<string, string> = {
        pangolinEndpoint: values.pangolinEndpoint,
        pangolinNewtId: values.pangolinNewtId,
      };
      if (values.pangolinNewtSecret) {
        payload.pangolinNewtSecret = values.pangolinNewtSecret;
      }
      await api.post(`/instances/${id}/tunnel`, payload);
      message.success('Tunnel configured');
      fetchInstance();
      tunnelForm.setFieldValue('pangolinNewtSecret', '');
    } catch (err: unknown) {
      const resp = (err as { response?: { data?: { error?: { message?: string } } } })?.response?.data?.error;
      message.error(resp?.message || 'Failed to configure tunnel');
    } finally {
      setTunnelSaving(false);
    }
  };

  const handleRemoveTunnel = async () => {
    setTunnelRemoving(true);
    try {
      await api.delete(`/instances/${id}/tunnel`);
      message.success('Tunnel removed');
      fetchInstance();
      tunnelForm.resetFields();
    } catch (err: unknown) {
      const resp = (err as { response?: { data?: { error?: { message?: string } } } })?.response?.data?.error;
      message.error(resp?.message || 'Failed to remove tunnel');
    } finally {
      setTunnelRemoving(false);
    }
  };

  const remoteTunnelTab = (
    <Space direction="vertical" size="large" style={{ width: '100%' }}>
      {tunnelStatus?.configured ? (
        <>
          <Alert
            message={`Tunnel active — ${tunnelStatus.online ? 'online' : 'offline'}`}
            description={`Connected to ${tunnelStatus.endpoint || instance.pangolinEndpoint} (site: ${tunnelStatus.siteId})`}
            type={tunnelStatus.online ? 'success' : 'warning'}
            showIcon
            icon={<CloudOutlined />}
          />

          <Card title="Current Configuration" size="small">
            <Descriptions bordered column={1}>
              <Descriptions.Item label="Endpoint">
                <Typography.Text copyable>{tunnelStatus.endpoint || instance.pangolinEndpoint}</Typography.Text>
              </Descriptions.Item>
              <Descriptions.Item label="Site ID">
                <Typography.Text copyable>{tunnelStatus.siteId || instance.pangolinSiteId}</Typography.Text>
              </Descriptions.Item>
              <Descriptions.Item label="Newt ID">
                <Typography.Text copyable>{instance.pangolinNewtId}</Typography.Text>
              </Descriptions.Item>
              <Descriptions.Item label="Status">
                <Tag color={tunnelStatus.online ? 'green' : 'orange'}>{tunnelStatus.online ? 'Online' : 'Offline'}</Tag>
              </Descriptions.Item>
            </Descriptions>
          </Card>

          {tunnelStatus.resources && tunnelStatus.resources.length > 0 && (
            <Card
              title="Resources"
              size="small"
              extra={
                <Space>
                  <Button icon={<SyncOutlined />} size="small" onClick={handleTunnelSync} loading={tunnelSyncing}>
                    Sync
                  </Button>
                  <Button icon={<ReloadOutlined />} size="small" onClick={fetchTunnelStatus} loading={tunnelStatusLoading}>
                    Refresh
                  </Button>
                </Space>
              }
            >
              <Table
                dataSource={tunnelStatus.resources}
                rowKey="resourceId"
                size="small"
                pagination={false}
                columns={[
                  { title: 'Subdomain', dataIndex: 'subdomain', render: (s: string) => s || '(root)' },
                  { title: 'Name', dataIndex: 'name' },
                  { title: 'Target', render: (_: unknown, r: { hasTarget: boolean; targetIp?: string; targetPort?: number }) =>
                    r.hasTarget ? `${r.targetIp}:${r.targetPort}` : <Tag color="red">No target</Tag>
                  },
                ]}
              />
            </Card>
          )}

          <Popconfirm
            title="Tear down tunnel?"
            description="This will delete the Pangolin site and all resources. The Newt container will be stopped."
            onConfirm={handleRemoteTunnelTeardown}
          >
            <Button danger icon={<DisconnectOutlined />} loading={tunnelRemoving}>
              Teardown Tunnel
            </Button>
          </Popconfirm>
        </>
      ) : (
        <>
          <Alert
            message="No tunnel configured"
            description="The CCP will create a Pangolin site and resources for this instance, push Newt credentials to its .env, and start the tunnel container."
            type="info"
            showIcon
          />

          <Card title="Import Existing Tunnel" size="small">
            <p style={{ marginTop: 0 }}>
              If this instance already has a Pangolin tunnel set up (e.g. by
              <code> config.sh --pangolin-site new</code> during install), the CCP can
              adopt it by reading the remote <code>.env</code> and verifying the site
              exists in the CCP&apos;s Pangolin org. No resources are modified.
            </p>
            <Popconfirm
              title="Import existing tunnel?"
              description="The CCP will read Pangolin credentials from the remote .env and persist them on this instance."
              onConfirm={handleTunnelImport}
              okText="Import"
            >
              <Button icon={<CloudOutlined />} loading={tunnelImporting}>
                Import Existing Tunnel
              </Button>
            </Popconfirm>
          </Card>

          <Card title="Setup Tunnel" size="small">
            <Form layout="vertical" onFinish={handleRemoteTunnelSetup}>
              <Form.Item
                name="subdomainPrefix"
                label="Subdomain Prefix (optional)"
                extra={`Leave empty for standard subdomains (app.${instance.domain}, api.${instance.domain}). Set a prefix for multi-tenant domains (e.g. "ck" creates ck-app.${instance.domain}).`}
                rules={[{ pattern: /^[a-z0-9-]*$/, message: 'Lowercase alphanumeric + hyphens only' }]}
              >
                <Input placeholder="(none — uses standard subdomains)" />
              </Form.Item>
              <Form.Item style={{ marginBottom: 0 }}>
                <Button type="primary" htmlType="submit" icon={<CloudOutlined />} loading={tunnelSetupRunning}>
                  Setup Tunnel
                </Button>
              </Form.Item>
            </Form>
          </Card>
        </>
      )}
    </Space>
  );

  const localTunnelTab = (
    <Space direction="vertical" size="large" style={{ width: '100%' }}>
      {!isManaged && (
        <Alert
          message="Tunnel management is not available for external instances"
          description="This instance was deployed outside the control panel. Manage its tunnel configuration directly."
          type="info"
          showIcon
        />
      )}

      {isManaged && tunnelConfigured && (
        <Alert
          message={`Tunnel active — connected to ${instance.pangolinEndpoint}`}
          type="success"
          showIcon
          icon={<CloudOutlined />}
        />
      )}

      {isManaged && !tunnelConfigured && (
        <Alert
          message="No tunnel configured"
          description="Enter your Pangolin Newt credentials below to enable tunnel access for this instance. You can get these from your Pangolin dashboard."
          type="info"
          showIcon
        />
      )}

      {tunnelConfigured && (
        <Card title="Current Configuration" size="small">
          <Descriptions bordered column={1}>
            <Descriptions.Item label="Pangolin Endpoint">
              <Typography.Text copyable>{instance.pangolinEndpoint}</Typography.Text>
            </Descriptions.Item>
            <Descriptions.Item label="Newt ID">
              <Typography.Text copyable>{instance.pangolinNewtId}</Typography.Text>
            </Descriptions.Item>
            <Descriptions.Item label="Newt Secret">
              <Tag color="green">Configured</Tag>
            </Descriptions.Item>
          </Descriptions>
        </Card>
      )}

      {canConfigureTunnel && !isRemote && (
        <Card title={tunnelConfigured ? 'Update Tunnel' : 'Enable Tunnel'} size="small">
          <Form
            form={tunnelForm}
            layout="vertical"
            onFinish={handleConfigureTunnel}
          >
            <Form.Item
              name="pangolinEndpoint"
              label="Pangolin Endpoint"
              rules={[
                { required: true, message: 'Endpoint is required' },
                { type: 'url', message: 'Must be a valid URL' },
              ]}
            >
              <Input placeholder="https://pangolin.example.com" />
            </Form.Item>
            <Form.Item
              name="pangolinNewtId"
              label="Newt ID"
              rules={[{ required: true, message: 'Newt ID is required' }]}
            >
              <Input placeholder="Newt ID from Pangolin dashboard" />
            </Form.Item>
            <Form.Item
              name="pangolinNewtSecret"
              label="Newt Secret"
              rules={tunnelConfigured ? [] : [{ required: true, message: 'Secret is required for first-time setup' }]}
              extra={tunnelConfigured ? 'Leave blank to keep the current secret' : undefined}
            >
              <Input.Password
                placeholder={tunnelConfigured ? 'Leave blank to keep current' : 'Newt secret from Pangolin dashboard'}
              />
            </Form.Item>
            <Form.Item style={{ marginBottom: 0 }}>
              <Space>
                <Button
                  type="primary"
                  htmlType="submit"
                  icon={<CloudOutlined />}
                  loading={tunnelSaving}
                >
                  {tunnelConfigured ? 'Update Tunnel' : 'Enable Tunnel'}
                </Button>
                {tunnelConfigured && (
                  <Popconfirm
                    title="Remove tunnel?"
                    description="This will remove the Newt container and all tunnel configuration."
                    onConfirm={handleRemoveTunnel}
                  >
                    <Button
                      danger
                      icon={<DisconnectOutlined />}
                      loading={tunnelRemoving}
                    >
                      Remove Tunnel
                    </Button>
                  </Popconfirm>
                )}
              </Space>
            </Form.Item>
          </Form>
        </Card>
      )}
    </Space>
  );

  const tunnelTab = isRemote ? remoteTunnelTab : localTunnelTab;

  // ─── Updates Tab ──────────────────────────────────────────────

  const isUpgrading = currentUpgrade?.status === 'IN_PROGRESS' || currentUpgrade?.status === 'PENDING';

  const upgradeStatusColors: Record<string, string> = {
    PENDING: 'default',
    IN_PROGRESS: 'processing',
    COMPLETED: 'green',
    FAILED: 'red',
    ROLLED_BACK: 'orange',
  };

  const updatesTab = (
    <Space direction="vertical" size="large" style={{ width: '100%' }}>
      {/* Version Info Card */}
      <Card
        title="Version Information"
        size="small"
        extra={
          <Button
            icon={<ReloadOutlined />}
            onClick={handleCheckUpdate}
            loading={checkingUpdate}
            disabled={isUpgrading}
          >
            Check for Updates
          </Button>
        }
      >
        <Descriptions bordered column={{ xs: 1, sm: 2 }}>
          <Descriptions.Item label="Branch">{instance.gitBranch}</Descriptions.Item>
          <Descriptions.Item label="Current Commit">
            <Typography.Text code>{instance.gitCommit || updateStatus?.currentCommit || 'Unknown'}</Typography.Text>
          </Descriptions.Item>
          {updateStatus && (
            <>
              <Descriptions.Item label="Remote Commit">
                <Typography.Text code>{updateStatus.remoteCommit || 'N/A'}</Typography.Text>
              </Descriptions.Item>
              <Descriptions.Item label="Updates Available">
                {updateStatus.commitsBehind > 0 ? (
                  <Tag color="blue">{updateStatus.commitsBehind} commit{updateStatus.commitsBehind !== 1 ? 's' : ''} behind</Tag>
                ) : (
                  <Tag color="green">Up to date</Tag>
                )}
              </Descriptions.Item>
              {updateStatus.error && (
                <Descriptions.Item label="Check Error" span={2}>
                  <Typography.Text type="danger">{updateStatus.error}</Typography.Text>
                </Descriptions.Item>
              )}
              <Descriptions.Item label="Last Checked" span={2}>
                {dayjs(updateStatus.checkedAt).format('YYYY-MM-DD HH:mm:ss')}
              </Descriptions.Item>
            </>
          )}
        </Descriptions>
      </Card>

      {/* Changelog */}
      {updateStatus && updateStatus.changelog.length > 0 && updateStatus.commitsBehind > 0 && (
        <Card title="Changelog" size="small">
          {updateStatus.changelog.map((entry, i) => (
            <div key={i} style={{ marginBottom: 8 }}>
              <Space>
                <Typography.Text code style={{ fontSize: 12 }}>{entry.hash?.slice(0, 7)}</Typography.Text>
                <Typography.Text>{entry.message}</Typography.Text>
              </Space>
              <br />
              <Typography.Text type="secondary" style={{ fontSize: 11 }}>
                {entry.author} — {dayjs(entry.date).format('MM-DD HH:mm')}
              </Typography.Text>
            </div>
          ))}
        </Card>
      )}

      {/* Upgrade Action */}
      {isManaged && (
        <Card title="Upgrade" size="small">
          {isUpgrading && currentUpgrade ? (
            <Space direction="vertical" style={{ width: '100%' }}>
              <Alert
                message={`Upgrading — Phase ${currentUpgrade.currentPhase}: ${currentUpgrade.phaseName || 'Starting...'}`}
                description={currentUpgrade.progressMessage || 'Processing...'}
                type="info"
                showIcon
                icon={<SyncOutlined spin />}
              />
              <Progress
                percent={currentUpgrade.percentage}
                status="active"
                strokeColor={{ from: '#108ee9', to: '#87d068' }}
              />
            </Space>
          ) : (
            <Space direction="vertical" style={{ width: '100%' }}>
              {currentUpgrade?.status === 'FAILED' && currentUpgrade.errorMessage && (
                <Alert
                  message="Last Upgrade Failed"
                  description={currentUpgrade.errorMessage}
                  type="error"
                  showIcon
                  closable
                />
              )}
              {currentUpgrade?.status === 'COMPLETED' && (
                <Alert
                  message="Last Upgrade Succeeded"
                  description={`Updated to ${currentUpgrade.newCommit || 'latest'} (${currentUpgrade.durationSeconds ? `${Math.round(currentUpgrade.durationSeconds / 60)}min ${currentUpgrade.durationSeconds % 60}s` : 'duration unknown'})`}
                  type="success"
                  showIcon
                  closable
                />
              )}
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 16 }}>
                <Typography.Text type="secondary" style={{ flex: 1 }}>
                  Full upgrade pulls the latest code, runs migrations, and restarts services. Quick upgrade only pulls images and recreates the core app — tenant content stays untouched and it&apos;s ~2 min faster. Use Quick when the release notes say no orchestration changes.
                </Typography.Text>
                <Space>
                  <Popconfirm
                    title="Start quick (image-only) upgrade?"
                    description="Pulls new container images and recreates the API/Admin/Media/Nginx services. No filesystem changes — mkdocs and configs are not touched. Brief downtime is expected."
                    onConfirm={handleStartImageUpgrade}
                    disabled={instance.status !== 'RUNNING' && instance.status !== 'STOPPED'}
                  >
                    <Button
                      icon={<ThunderboltOutlined />}
                      loading={upgradingInstance}
                      disabled={instance.status !== 'RUNNING' && instance.status !== 'STOPPED'}
                    >
                      Quick Upgrade
                    </Button>
                  </Popconfirm>
                  <Button
                    icon={<CloudUploadOutlined />}
                    onClick={() => {
                      setReleaseImageTag(instance.imageTag || '');
                      setReleasePreview(null);
                      setReleaseUpgradeModalOpen(true);
                    }}
                    disabled={instance.status !== 'RUNNING' && instance.status !== 'STOPPED'}
                  >
                    Upgrade to Release
                  </Button>
                  <Popconfirm
                    title="Start full upgrade?"
                    description="This will pull the latest code, run database migrations, and restart all services. Brief downtime is expected."
                    onConfirm={handleStartUpgrade}
                    disabled={instance.status !== 'RUNNING' && instance.status !== 'STOPPED'}
                  >
                    <Button
                      type="primary"
                      icon={<UploadOutlined />}
                      loading={upgradingInstance}
                      disabled={instance.status !== 'RUNNING' && instance.status !== 'STOPPED'}
                    >
                      Upgrade Now
                    </Button>
                  </Popconfirm>
                </Space>
              </div>
            </Space>
          )}
        </Card>
      )}

      {!isManaged && (
        <Alert
          message="Upgrades are not managed by CCP for external instances"
          description="Run the upgrade script directly on the instance or use its own upgrade mechanism."
          type="info"
          showIcon
        />
      )}
      {isRemote && (
        <Alert
          message="Remote instance"
          description="Upgrades run via the remote agent over mTLS. The agent shells out to scripts/upgrade.sh --api-mode and the control panel polls progress every 2s."
          type="info"
          showIcon
        />
      )}

      {/* Upgrade History */}
      <Card title="Upgrade History" size="small">
        {upgradeHistory.length > 0 ? (
          <Table
            dataSource={upgradeHistory}
            rowKey="id"
            loading={upgradeHistoryLoading}
            size="small"
            pagination={false}
            columns={[
              {
                title: 'Status',
                dataIndex: 'status',
                width: 120,
                render: (s: string) => <Tag color={upgradeStatusColors[s]}>{s}</Tag>,
              },
              {
                title: 'Commits',
                render: (_: unknown, r: InstanceUpgrade) => (
                  <span>
                    <Typography.Text code style={{ fontSize: 11 }}>{r.previousCommit || '?'}</Typography.Text>
                    {' → '}
                    <Typography.Text code style={{ fontSize: 11 }}>{r.newCommit || '?'}</Typography.Text>
                  </span>
                ),
              },
              {
                title: 'Duration',
                dataIndex: 'durationSeconds',
                width: 90,
                render: (s: number | null) => s != null ? `${Math.floor(s / 60)}m ${s % 60}s` : '-',
              },
              {
                title: 'By',
                dataIndex: ['triggeredBy', 'name'],
                width: 120,
                render: (name: string) => name || '-',
              },
              {
                title: 'Date',
                dataIndex: 'startedAt',
                width: 140,
                render: (d: string) => dayjs(d).format('MM-DD HH:mm'),
              },
              {
                title: 'Warnings',
                width: 80,
                render: (_: unknown, r: InstanceUpgrade) => {
                  const count = (r.warnings as string[] | undefined)?.length || 0;
                  return count > 0 ? <Tag color="orange">{count}</Tag> : '-';
                },
              },
            ]}
          />
        ) : (
          <Empty description="No upgrades recorded" />
        )}
      </Card>
    </Space>
  );

  // ─── Events Tab ──────────────────────────────────────────────

  const severityIcons: Record<string, React.ReactNode> = {
    ERROR: <CloseCircleOutlined style={{ color: '#ff4d4f' }} />,
    WARNING: <WarningOutlined style={{ color: '#faad14' }} />,
    INFO: <InfoCircleOutlined style={{ color: '#1677ff' }} />,
  };

  const severityColors: Record<string, string> = {
    ERROR: 'red',
    WARNING: 'orange',
    INFO: 'blue',
  };

  const unacknowledgedCount = events.filter((e) => !e.acknowledged).length;

  const eventsTab = (
    <Space direction="vertical" size="large" style={{ width: '100%' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 8 }}>
        <Space>
          <Typography.Text type="secondary">
            {eventsTotal} event{eventsTotal !== 1 ? 's' : ''}
          </Typography.Text>
          {unacknowledgedCount > 0 && (
            <Tag color="red">{unacknowledgedCount} unacknowledged</Tag>
          )}
        </Space>
        <Space>
          {unacknowledgedCount > 0 && (
            <Popconfirm
              title={`Acknowledge all ${unacknowledgedCount} event(s)?`}
              onConfirm={handleAcknowledgeAll}
            >
              <Button icon={<CheckCircleOutlined />} size="small">
                Acknowledge All
              </Button>
            </Popconfirm>
          )}
          <Button icon={<ReloadOutlined />} onClick={fetchEvents} loading={eventsLoading} size="small">
            Refresh
          </Button>
        </Space>
      </div>

      {events.length > 0 ? (
        <Table
          dataSource={events}
          rowKey="id"
          loading={eventsLoading}
          size="small"
          pagination={{ pageSize: 20 }}
          columns={[
            {
              title: 'Severity',
              dataIndex: 'severity',
              width: 100,
              filters: [
                { text: 'Error', value: 'ERROR' },
                { text: 'Warning', value: 'WARNING' },
                { text: 'Info', value: 'INFO' },
              ],
              onFilter: (value, record) => record.severity === value,
              render: (s: string) => (
                <Tag color={severityColors[s]} icon={severityIcons[s]}>
                  {s}
                </Tag>
              ),
            },
            {
              title: 'Source',
              dataIndex: 'source',
              width: 120,
              render: (s: string) => <Tag>{s.replace('_', ' ')}</Tag>,
            },
            {
              title: 'Title',
              dataIndex: 'title',
              ellipsis: true,
            },
            {
              title: 'Message',
              dataIndex: 'message',
              ellipsis: true,
              render: (msg: string) => (
                <Typography.Text type="secondary" style={{ fontSize: 12 }} ellipsis={{ tooltip: msg }}>
                  {msg}
                </Typography.Text>
              ),
            },
            {
              title: 'Time',
              dataIndex: 'createdAt',
              width: 140,
              render: (d: string) => dayjs(d).format('MM-DD HH:mm:ss'),
            },
            {
              title: 'Status',
              width: 120,
              render: (_: unknown, record: InstanceEvent) => (
                record.acknowledged ? (
                  <Tag color="default" icon={<CheckCircleOutlined />}>Acknowledged</Tag>
                ) : (
                  <Button
                    size="small"
                    type="link"
                    icon={<CheckCircleOutlined />}
                    onClick={() => handleAcknowledgeEvent(record.id)}
                  >
                    Acknowledge
                  </Button>
                )
              ),
            },
          ]}
        />
      ) : (
        <Empty description="No events recorded" />
      )}
    </Space>
  );

  const secretEntries = secrets ? Object.entries(secrets) : [];
  const isRegisteredSecrets = isRegistered;

  const credentialsTab = (
    <Space direction="vertical" size="large" style={{ width: '100%' }}>
      {!passwordVerified && !secretsFetched ? (
        <div style={{ textAlign: 'center', padding: '64px 0' }}>
          <LockOutlined style={{ fontSize: 48, color: '#999', marginBottom: 16 }} />
          <Typography.Title level={4} type="secondary">
            Password Required
          </Typography.Title>
          <Typography.Paragraph type="secondary">
            Re-enter your password to view instance credentials.
          </Typography.Paragraph>
          <Button
            type="primary"
            icon={<LockOutlined />}
            onClick={() => setPasswordModalOpen(true)}
          >
            Unlock Credentials
          </Button>
        </div>
      ) : (
        <>
          <Alert
            message="Initial Credentials"
            description={
              isRegistered
                ? 'These credentials are read from the instance\'s .env file on disk. If the admin user has changed their password since setup, the password shown here will be outdated.'
                : 'These are the secrets generated when the instance was provisioned by CCP. If the admin user has changed their password, the initial password may be outdated.'
            }
            type="warning"
            showIcon
          />

          {secretsLoading && (
            <div style={{ display: 'flex', justifyContent: 'center', padding: 48 }}>
              <Spin tip="Loading credentials..." />
            </div>
          )}

          {secretsError && (
            <Alert
              message="Could not load credentials"
              description={secretsError}
              type="error"
              showIcon
              action={
                <Button size="small" onClick={fetchSecrets}>
                  Retry
                </Button>
              }
            />
          )}

          {secrets && !secretsLoading && (
            <>
              {isRegisteredSecrets ? (
                <Card title="Admin Credentials" size="small">
                  <Descriptions bordered column={1}>
                    <Descriptions.Item label="Admin Email">
                      <Typography.Text copyable>
                        {secrets.initialAdminEmail || 'Not set'}
                      </Typography.Text>
                    </Descriptions.Item>
                    <Descriptions.Item label="Admin Password">
                      <Space>
                        <Input.Password
                          value={secrets.initialAdminPassword || 'Not set'}
                          readOnly
                          style={{ width: 300 }}
                        />
                        <Button
                          icon={<CopyOutlined />}
                          size="small"
                          type="text"
                          onClick={() => {
                            if (secrets.initialAdminPassword) {
                              navigator.clipboard.writeText(secrets.initialAdminPassword);
                              message.success('Password copied');
                            }
                          }}
                          disabled={!secrets.initialAdminPassword}
                        />
                      </Space>
                    </Descriptions.Item>
                  </Descriptions>
                </Card>
              ) : (
                <Card
                  title="Instance Secrets"
                  size="small"
                  extra={
                    <Button
                      icon={revealAll ? <EyeInvisibleOutlined /> : <EyeOutlined />}
                      size="small"
                      onClick={() => setRevealAll((v) => !v)}
                    >
                      {revealAll ? 'Hide All' : 'Reveal All'}
                    </Button>
                  }
                >
                  <Descriptions bordered column={1} size="small">
                    {secretEntries.map(([key, value]) => (
                      <Descriptions.Item key={key} label={key}>
                        <Space>
                          {revealAll ? (
                            <Typography.Text code style={{ maxWidth: 400, wordBreak: 'break-all' }}>
                              {String(value ?? 'null')}
                            </Typography.Text>
                          ) : (
                            <Input.Password
                              value={String(value ?? '')}
                              readOnly
                              style={{ width: 300 }}
                            />
                          )}
                          <Button
                            icon={<CopyOutlined />}
                            size="small"
                            type="text"
                            onClick={() => {
                              if (value) {
                                navigator.clipboard.writeText(String(value));
                                message.success(`${key} copied`);
                              }
                            }}
                            disabled={!value}
                          />
                        </Space>
                      </Descriptions.Item>
                    ))}
                  </Descriptions>
                </Card>
              )}
            </>
          )}
        </>
      )}

      <Modal
        title="Confirm Your Password"
        open={passwordModalOpen}
        onCancel={() => {
          setPasswordModalOpen(false);
          passwordForm.resetFields();
          if (!passwordVerified) setActiveTab('overview');
        }}
        footer={null}
        destroyOnHidden
      >
        <Typography.Paragraph type="secondary">
          For security, please re-enter your password to access instance credentials.
        </Typography.Paragraph>
        <Form form={passwordForm} onFinish={handlePasswordVerify} layout="vertical">
          <Form.Item
            name="password"
            label="Password"
            rules={[{ required: true, message: 'Please enter your password' }]}
          >
            <Input.Password
              placeholder="Enter your CCP password"
              autoFocus
              size="large"
            />
          </Form.Item>
          <Form.Item style={{ marginBottom: 0, textAlign: 'right' }}>
            <Space>
              <Button onClick={() => {
                setPasswordModalOpen(false);
                passwordForm.resetFields();
                if (!passwordVerified) setActiveTab('overview');
              }}>
                Cancel
              </Button>
              <Button type="primary" htmlType="submit" loading={verifying}>
                Verify
              </Button>
            </Space>
          </Form.Item>
        </Form>
      </Modal>
    </Space>
  );

  return (
    <div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 16, marginBottom: 24, flexWrap: 'wrap' }}>
        <Button icon={<ArrowLeftOutlined />} onClick={() => navigate('/app/instances')} />
        <Typography.Title level={3} style={{ margin: 0 }}>
          {instance.name}
        </Typography.Title>
        <Tag color={statusColors[instance.status]}>{instance.status}</Tag>
        {isRegistered && <Tag color="purple">External</Tag>}
        <div style={{ flex: 1 }} />
        <Space wrap>
          {canStart && (
            <Button
              icon={<PlayCircleOutlined />}
              type="primary"
              onClick={() => handleAction('start', 'Start')}
              loading={actionLoading === 'start'}
            >
              Start
            </Button>
          )}
          {canStop && (
            <Popconfirm
              title="Stop this instance?"
              description="All containers will be stopped."
              onConfirm={() => handleAction('stop', 'Stop')}
            >
              <Button
                icon={<PauseCircleOutlined />}
                danger
                loading={actionLoading === 'stop'}
              >
                Stop
              </Button>
            </Popconfirm>
          )}
          {canRestart && (
            <Button
              icon={<SyncOutlined />}
              onClick={() => handleAction('restart', 'Restart')}
              loading={actionLoading === 'restart'}
            >
              Restart
            </Button>
          )}
          <Button
            icon={<LinkOutlined />}
            onClick={() => window.open(`http://localhost:${ports.admin}`, '_blank')}
          >
            Open Local
          </Button>
          <Button
            icon={<LinkOutlined />}
            onClick={() => window.open(`https://app.${instance.domain}`, '_blank')}
          >
            Open Tunnel
          </Button>
          <Button icon={<ReloadOutlined />} onClick={fetchInstance}>
            Refresh
          </Button>
        </Space>
      </div>

      <Tabs
        activeKey={activeTab}
        onChange={setActiveTab}
        items={[
          { key: 'overview', label: 'Overview', children: overviewTab },
          { key: 'updates', label: 'Updates', icon: <UploadOutlined />, children: updatesTab },
          { key: 'events', label: unacknowledgedCount > 0 ? `Events (${unacknowledgedCount})` : 'Events', icon: <BellOutlined />, children: eventsTab },
          { key: 'credentials', label: 'Credentials', icon: <LockOutlined />, children: credentialsTab },
          { key: 'features', label: 'Features', children: featuresTab },
          { key: 'services', label: 'Services', children: servicesTab },
          { key: 'logs', label: 'Logs', children: logsTab },
          { key: 'backups', label: 'Backups', children: backupsTab },
          { key: 'tunnel', label: 'Tunnel', children: tunnelTab },
        ]}
      />

      {/* Restore confirmation modal (destructive action guard) */}
      <Modal
        title="Restore backup — destructive"
        open={!!restoreModal}
        onCancel={() => setRestoreModal(null)}
        onOk={handleRestoreConfirm}
        okText="Restore"
        okButtonProps={{
          danger: true,
          loading: restoring,
          disabled: restoreModal?.typedSlug !== instance.slug,
        }}
        cancelButtonProps={{ disabled: restoring }}
        width={560}
      >
        <Alert
          type="error"
          showIcon
          message="This will OVERWRITE the instance's databases and uploads"
          description="The agent will stop application containers, drop databases, and restore from the selected backup. This cannot be undone without another backup."
          style={{ marginBottom: 16 }}
        />
        {restoreModal && (
          <Descriptions column={1} size="small" bordered style={{ marginBottom: 16 }}>
            <Descriptions.Item label="Backup ID">
              <code>{restoreModal.backup.id.substring(0, 8)}</code>
            </Descriptions.Item>
            <Descriptions.Item label="Archive size">
              {restoreModal.backup.sizeBytes
                ? `${(Number(restoreModal.backup.sizeBytes) / 1024 / 1024).toFixed(1)} MB`
                : '-'}
            </Descriptions.Item>
            <Descriptions.Item label="Created">
              {restoreModal.backup.completedAt
                ? dayjs(restoreModal.backup.completedAt).format('YYYY-MM-DD HH:mm')
                : '-'}
            </Descriptions.Item>
          </Descriptions>
        )}
        <Typography.Paragraph>
          Type the instance slug <strong><code>{instance.slug}</code></strong> to confirm:
        </Typography.Paragraph>
        <Input
          value={restoreModal?.typedSlug || ''}
          onChange={(e) =>
            setRestoreModal((cur) => (cur ? { ...cur, typedSlug: e.target.value } : cur))
          }
          placeholder={instance.slug}
          autoFocus
        />
      </Modal>

      {/* Active restore progress banner */}
      {activeRestoreId && activeRestoreState && (
        <Modal
          title="Restore in progress"
          open
          closable={false}
          footer={null}
          width={640}
        >
          <Space direction="vertical" style={{ width: '100%' }}>
            <div>
              <Tag
                color={
                  activeRestoreState.status === 'COMPLETED'
                    ? 'green'
                    : activeRestoreState.status === 'FAILED'
                    ? 'red'
                    : 'processing'
                }
              >
                {activeRestoreState.status}
              </Tag>
              {activeRestoreState.status === 'RUNNING' && (
                <Typography.Text type="secondary" style={{ marginLeft: 8 }}>
                  Agent is running scripts/restore.sh — this can take several minutes
                </Typography.Text>
              )}
            </div>
            {activeRestoreState.errorMessage && (
              <Alert type="error" message={activeRestoreState.errorMessage} showIcon />
            )}
            {activeRestoreState.logTail && (
              <pre
                style={{
                  background: '#1e1e1e',
                  color: '#d4d4d4',
                  padding: 12,
                  maxHeight: 300,
                  overflow: 'auto',
                  fontSize: 12,
                  borderRadius: 4,
                }}
              >
                {activeRestoreState.logTail}
              </pre>
            )}
          </Space>
        </Modal>
      )}

      {/* Approach C: Release upgrade modal (CCP template re-render) */}
      <Modal
        title="Upgrade to Release"
        open={releaseUpgradeModalOpen}
        onCancel={() => setReleaseUpgradeModalOpen(false)}
        footer={[
          <Button key="cancel" onClick={() => setReleaseUpgradeModalOpen(false)}>
            Cancel
          </Button>,
          <Button
            key="preview"
            onClick={handlePreviewReleaseUpgrade}
            loading={releasePreviewLoading}
          >
            Preview Changes
          </Button>,
          <Button
            key="apply"
            type="primary"
            danger={
              !!releasePreview?.envCoverage?.missingInTenantEnv?.length
            }
            loading={upgradingInstance}
            onClick={handleStartReleaseUpgrade}
          >
            Apply Upgrade
          </Button>,
        ]}
        width={900}
      >
        <Space direction="vertical" style={{ width: '100%' }} size="middle">
          <div>
            <Typography.Text strong>Image Tag:</Typography.Text>
            <Input
              value={releaseImageTag}
              onChange={(e) => setReleaseImageTag(e.target.value)}
              placeholder="e.g. v2.10.3 (blank = use current env.IMAGE_TAG default)"
              style={{ marginTop: 4 }}
            />
            <Typography.Text type="secondary" style={{ fontSize: 12 }}>
              Re-renders docker-compose.yml + env + nginx configs with this tag. Tenant content
              (mkdocs/, custom configs/) is never touched. Click <em>Preview Changes</em> to see the
              per-file diff before applying.
            </Typography.Text>
          </div>

          {releasePreview && (
            <>
              {releasePreview.envCoverage?.missingInTenantEnv && releasePreview.envCoverage.missingInTenantEnv.length > 0 && (
                <Alert
                  type="error"
                  showIcon
                  message="Missing env vars in tenant .env"
                  description={
                    <div>
                      <div>The new docker-compose.yml references vars the tenant&apos;s .env does NOT define:</div>
                      <code style={{ display: 'block', marginTop: 8, fontSize: 11 }}>
                        {releasePreview.envCoverage.missingInTenantEnv.join(', ')}
                      </code>
                      <div style={{ marginTop: 8 }}>
                        Applying without these vars may break services. Add them to the tenant&apos;s .env
                        first, or reconcile the template.
                      </div>
                    </div>
                  }
                />
              )}

              <Typography.Text strong>
                Files: {releasePreview.files.length} total, {releasePreview.files.filter(f => f.status === 'modified').length} modified, {releasePreview.files.filter(f => f.status === 'created').length} created, {releasePreview.files.filter(f => f.status === 'unchanged').length} unchanged
              </Typography.Text>

              <div style={{ maxHeight: 500, overflow: 'auto', border: '1px solid #303030', borderRadius: 4 }}>
                {releasePreview.files.map((f) => (
                  <div key={f.path} style={{ padding: 8, borderBottom: '1px solid #303030' }}>
                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                      <code style={{ fontSize: 12 }}>{f.path}</code>
                      <Tag
                        color={
                          f.status === 'unchanged' ? 'green' :
                          f.status === 'modified' ? 'gold' :
                          'blue'
                        }
                      >
                        {f.status} {f.sizeBefore !== undefined && `(${f.sizeBefore} → ${f.sizeAfter} B)`}
                      </Tag>
                    </div>
                    {f.diff && (
                      <pre
                        style={{
                          background: '#1e1e1e',
                          color: '#d4d4d4',
                          padding: 8,
                          marginTop: 8,
                          maxHeight: 200,
                          overflow: 'auto',
                          fontSize: 11,
                          borderRadius: 4,
                        }}
                      >
                        {f.diff}
                      </pre>
                    )}
                  </div>
                ))}
              </div>
            </>
          )}
        </Space>
      </Modal>
    </div>
  );
}
