'use client';

import { useState } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import {
  AlertTriangle,
  CheckCircle2,
  HardDrive,
  Loader2,
  RefreshCw,
  ServerCog,
  XCircle,
} from 'lucide-react';
import { toast } from 'sonner';

import { PageHeader } from '@/components/shared/page-header';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog';
import {
  SettingsFormCard,
  type SettingFieldDef,
} from '@/components/admin/shared/settings-form-card';
import { RegistryDrivenForm } from '@/components/admin/shared/registry-driven-form';
import { WarningCallout } from '@/components/ui/warning-callout';
import { apiFetch } from '@/lib/api/client';
import { toastError } from '@/lib/api/toast-error';

type BackendName = 's3' | 'filesystem';

interface StorageStatus {
  backend: BackendName;
  fileCount: number;
  totalBytes: number;
  tablesTracked: string[];
}

interface MigrationResult {
  rowsConsidered: number;
  rowsMigrated: number;
  rowsSkippedAlreadyDone: number;
  totalBytes: number;
  flipped: boolean;
  dryRun: boolean;
}

const S3_FIELDS: SettingFieldDef[] = [
  {
    key: 'storage_s3_endpoint',
    label: 'S3 endpoint URL',
    description:
      'For AWS use https://s3.<region>.amazonaws.com. For MinIO/Backblaze/R2/Wasabi/Tigris paste the provider-supplied URL.',
    type: 'string',
    placeholder: 'https://s3.us-east-1.amazonaws.com',
    defaultValue: '',
  },
  {
    key: 'storage_s3_region',
    label: 'S3 region',
    description: 'AWS region code (e.g. us-east-1, eu-west-1). Use "auto" for Cloudflare R2.',
    type: 'string',
    placeholder: 'us-east-1',
    defaultValue: 'us-east-1',
  },
  {
    key: 'storage_s3_bucket',
    label: 'S3 bucket name',
    description: 'Existing bucket the CRM should read/write to. Must already exist.',
    type: 'string',
    placeholder: 'port-nimara-storage',
    defaultValue: '',
  },
  {
    // Reads from the legacy plaintext key for backward compat. New writes
    // go through the registry-driven form below (which uses the
    // `*_encrypted` key + AES at rest). After running the migration script
    // (`pnpm tsx scripts/encrypt-plaintext-credentials.ts`) this field is
    // empty and the encrypted form takes over.
    key: 'storage_s3_access_key',
    label: 'S3 access key (legacy plaintext  -  deprecated)',
    description:
      'Deprecated. Use the AES-encrypted access key field below instead. After running the migration script, this row is removed and only the encrypted form is used.',
    type: 'string',
    placeholder: 'AKIA…',
    defaultValue: '',
  },
  {
    // M-S01: encrypted at rest like the secret key. The legacy plaintext
    // field above is reflected for backward compat but new writes go
    // through this AES envelope.
    key: 'storage_s3_access_key_encrypted',
    label: 'S3 access key (encrypted)',
    description:
      'Stored AES-encrypted at rest; the field shows blank after save and is replaced only when you type a new value. Run `pnpm tsx scripts/encrypt-plaintext-credentials.ts` to migrate the legacy plaintext value into this field.',
    type: 'password',
    placeholder: '(unchanged)',
    defaultValue: '',
  },
  {
    key: 'storage_s3_secret_key_encrypted',
    label: 'S3 secret key',
    description:
      'Stored AES-encrypted at rest; the field shows blank after save and is replaced only when you type a new value.',
    type: 'password',
    placeholder: '(unchanged)',
    defaultValue: '',
  },
  {
    key: 'storage_s3_force_path_style',
    label: 'Force path-style URLs',
    description:
      'On for MinIO and most self-hosted S3-compatible servers. Off for AWS S3 (which uses virtual-hosted-style by default).',
    type: 'boolean',
    defaultValue: false,
  },
];

const FS_FIELDS: SettingFieldDef[] = [
  {
    key: 'storage_filesystem_root',
    label: 'Filesystem root path',
    description:
      'Absolute path on the server where files are stored. Must be writable by the CRM process. Single-node deployments only.',
    type: 'string',
    placeholder: '/var/lib/port-nimara/storage',
    defaultValue: '/var/lib/port-nimara/storage',
  },
];

export function StorageAdminPanel() {
  const queryClient = useQueryClient();
  const [confirmOpen, setConfirmOpen] = useState(false);
  const [dryRun, setDryRun] = useState<MigrationResult | null>(null);
  const [confirmMode, setConfirmMode] = useState<'switch-only' | 'switch-and-migrate'>(
    'switch-and-migrate',
  );
  const [testResult, setTestResult] = useState<{ ok: boolean; error?: string } | null>(null);

  const status = useQuery({
    queryKey: ['admin', 'storage', 'status'],
    queryFn: () => apiFetch<{ data: StorageStatus }>('/api/v1/admin/storage'),
  });

  const dryRunMutation = useMutation({
    mutationFn: async (opts: { from: BackendName; to: BackendName }) =>
      apiFetch<{ data: MigrationResult }>('/api/v1/admin/storage/migrate', {
        method: 'POST',
        body: { ...opts, dryRun: true },
      }),
    onSuccess: (result) => {
      setDryRun(result.data);
      setConfirmOpen(true);
    },
    onError: (e) => toastError(e),
  });

  const migrateMutation = useMutation({
    mutationFn: async (opts: { from: BackendName; to: BackendName; skipMigration: boolean }) =>
      apiFetch<{ data: MigrationResult }>('/api/v1/admin/storage/migrate', {
        method: 'POST',
        body: { ...opts, dryRun: false },
      }),
    onSuccess: (result) => {
      setConfirmOpen(false);
      setDryRun(null);
      const copied = result.data.rowsMigrated ?? 0;
      toast.success(
        copied > 0
          ? `Storage migration complete (${copied} file${copied === 1 ? '' : 's'} copied)`
          : 'Storage backend switched (no migration performed)',
      );
      queryClient.invalidateQueries({ queryKey: ['admin', 'storage', 'status'] });
    },
    onError: (e) => toastError(e),
  });

  const testMutation = useMutation({
    mutationFn: async () =>
      apiFetch<{ ok: boolean; error?: string }>('/api/v1/admin/storage', {
        method: 'POST',
      }),
    onSuccess: (r) => setTestResult(r),
    onError: (e: Error) => setTestResult({ ok: false, error: e.message }),
  });

  if (status.isLoading) {
    return (
      <div className="flex items-center gap-2 text-sm text-muted-foreground">
        <Loader2 className="h-4 w-4 animate-spin" aria-hidden /> Loading storage status…
      </div>
    );
  }
  if (status.isError || !status.data?.data) {
    return <div className="text-sm text-destructive">Failed to load storage status.</div>;
  }
  const s = status.data.data;
  const otherBackend: BackendName = s.backend === 's3' ? 'filesystem' : 's3';

  function openConfirm(mode: 'switch-only' | 'switch-and-migrate') {
    setConfirmMode(mode);
    if (mode === 'switch-and-migrate') {
      // Dry run first so the dialog shows the exact rows + bytes.
      dryRunMutation.mutate({ from: s.backend, to: otherBackend });
    } else {
      // Switch-only - no dry run, just show the warning.
      setDryRun(null);
      setConfirmOpen(true);
    }
  }

  return (
    <div className="space-y-6">
      <PageHeader
        title="Storage Backend"
        description="Where the CRM stores per-berth PDFs, brochures, GDPR exports, profile photos, and other binary files."
      />

      {/* AES-encrypted access key  -  write path. The legacy plaintext access
          key field below is read-only deprecation; new writes should go
          through this card. After running the encrypt-plaintext-credentials
          migration script, the legacy field becomes empty. */}
      <RegistryDrivenForm
        title="S3 access key (encrypted)"
        description="AES-encrypted at rest. Type your access key here  -  it replaces the deprecated plaintext field below and fixes audit finding S-23."
        sections={['storage.s3']}
      />

      {/* STEP 1: configure connection details for the OTHER backend so the
          admin can prep + test BEFORE attempting any switch. */}
      <SettingsFormCard
        title="S3 configuration"
        description="Provider connection details. Save these first, run Test connection, and only then switch the active backend below. You can edit S3 config while filesystem is active without touching live storage."
        fields={S3_FIELDS}
        extra={
          <div className="space-y-3 pt-2 border-t">
            <Button
              variant="outline"
              onClick={() => testMutation.mutate()}
              disabled={testMutation.isPending}
            >
              {testMutation.isPending && (
                <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden />
              )}
              Test S3 connection
            </Button>
            {testResult && (
              <div className="rounded-md border p-3 text-sm">
                {testResult.ok ? (
                  <div className="flex items-center gap-2 text-emerald-600">
                    <CheckCircle2 className="h-4 w-4" aria-hidden /> Connection OK - round-trip
                    succeeded.
                  </div>
                ) : (
                  <div className="flex items-center gap-2 text-destructive">
                    <XCircle className="h-4 w-4" aria-hidden />{' '}
                    {testResult.error ?? 'Connection failed'}
                  </div>
                )}
              </div>
            )}
          </div>
        }
      />

      <SettingsFormCard
        title="Filesystem configuration"
        description="Used when the active backend is filesystem. Only single-node deployments  -  multi-node servers must use S3."
        fields={FS_FIELDS}
      />

      {/* STEP 2: visualize current state + switch options. Both options
          are gated behind a confirmation dialog. */}
      <div className="grid gap-4 lg:grid-cols-3">
        <Card className="lg:col-span-2">
          <CardHeader className="flex flex-row items-start gap-3 space-y-0 pb-2">
            {s.backend === 's3' ? (
              <ServerCog className="mt-0.5 h-5 w-5 text-muted-foreground" aria-hidden />
            ) : (
              <HardDrive className="mt-0.5 h-5 w-5 text-muted-foreground" aria-hidden />
            )}
            <div>
              <CardTitle className="text-base">Active backend: {s.backend}</CardTitle>
              <CardDescription>
                {s.backend === 's3'
                  ? 'Files stored in an S3-compatible object store (AWS, MinIO, R2, B2, Wasabi, Tigris).'
                  : 'Files stored on the local filesystem under storage_filesystem_root. Single-node only.'}
              </CardDescription>
            </div>
          </CardHeader>
          <CardContent className="space-y-4">
            <dl className="grid grid-cols-2 gap-3 text-sm">
              <div>
                <dt className="text-muted-foreground">Tracked tables</dt>
                <dd>{s.tablesTracked.length === 0 ? '(none yet)' : s.tablesTracked.join(', ')}</dd>
              </div>
              <div>
                <dt className="text-muted-foreground">File count</dt>
                <dd>{s.fileCount}</dd>
              </div>
            </dl>

            <div className="space-y-2">
              <p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
                Switch active backend
              </p>
              <div className="flex flex-wrap gap-2">
                <Button
                  variant="outline"
                  disabled={dryRunMutation.isPending}
                  onClick={() => openConfirm('switch-and-migrate')}
                >
                  {dryRunMutation.isPending && (
                    <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden />
                  )}
                  Switch + migrate existing files to {otherBackend}
                </Button>
                <Button variant="outline" onClick={() => openConfirm('switch-only')}>
                  Switch only (new files only) → {otherBackend}
                </Button>
                <Button
                  variant="ghost"
                  onClick={() => status.refetch()}
                  disabled={status.isFetching}
                >
                  <RefreshCw className="mr-2 h-4 w-4" aria-hidden /> Refresh
                </Button>
              </div>
              <p className="text-xs text-muted-foreground">
                <strong>Switch + migrate</strong> copies every existing file to the new backend then
                flips the pointer atomically. Reversible with a follow-up reverse-migration.{' '}
                <strong>Switch only</strong> flips the pointer immediately - old files become
                inaccessible until you migrate them or revert the backend.
              </p>
            </div>
          </CardContent>
        </Card>

        <Card>
          <CardHeader>
            <CardTitle className="text-base">Backup notes</CardTitle>
          </CardHeader>
          <CardContent className="space-y-2 text-sm text-muted-foreground">
            {s.backend === 's3' ? (
              <p>
                S3 mode: configure your provider&apos;s lifecycle / replication / versioning
                policies as your primary backup. The CRM does not duplicate object storage in its
                own backups.
              </p>
            ) : (
              <p>
                Filesystem mode: include the storage root directory in your backup tool (restic,
                borg, snapshots). It sits next to the database; the two should be backed up
                together.
              </p>
            )}
            <p className="pt-2 text-xs">
              Filesystem mode refuses to start when MULTI_NODE_DEPLOYMENT=true. For multi-node
              deployments, switch to an S3-compatible backend.
            </p>
          </CardContent>
        </Card>
      </div>

      <Dialog open={confirmOpen} onOpenChange={setConfirmOpen}>
        <DialogContent>
          <DialogHeader>
            <DialogTitle className="flex items-center gap-2">
              <AlertTriangle className="h-5 w-5 text-amber-600" aria-hidden />
              {confirmMode === 'switch-and-migrate'
                ? `Switch + migrate to ${otherBackend}?`
                : `Switch active backend to ${otherBackend}?`}
            </DialogTitle>
            <DialogDescription>
              {confirmMode === 'switch-and-migrate'
                ? 'Copy every existing file to the new backend, verify each via sha256, then atomically flip the active backend. The dry-run summary below shows what will move.'
                : 'Flip the pointer immediately. Existing files stay on the old backend and become inaccessible until you migrate them or revert. Use this when you intentionally want a fresh storage tier (rare).'}
            </DialogDescription>
          </DialogHeader>
          {dryRun && confirmMode === 'switch-and-migrate' && (
            <div className="rounded-md border p-3 text-sm">
              <dl className="grid grid-cols-2 gap-2">
                <dt className="text-muted-foreground">Rows considered</dt>
                <dd>{dryRun.rowsConsidered}</dd>
                <dt className="text-muted-foreground">Already migrated (resumable)</dt>
                <dd>{dryRun.rowsSkippedAlreadyDone}</dd>
                <dt className="text-muted-foreground">Total bytes</dt>
                <dd>{Math.round(dryRun.totalBytes / 1024)} KB</dd>
              </dl>
            </div>
          )}
          {confirmMode === 'switch-only' && (
            <WarningCallout>
              {s.fileCount} existing file
              {s.fileCount === 1 ? '' : 's'} on <code className="text-xs">{s.backend}</code> will
              not be reachable from the CRM after the switch unless you migrate them later. This is
              rarely the right choice - prefer Switch + migrate.
            </WarningCallout>
          )}
          <DialogFooter>
            <Button variant="outline" onClick={() => setConfirmOpen(false)}>
              Cancel
            </Button>
            <Button
              disabled={migrateMutation.isPending}
              onClick={() =>
                migrateMutation.mutate({
                  from: s.backend,
                  to: otherBackend,
                  skipMigration: confirmMode === 'switch-only',
                })
              }
            >
              {migrateMutation.isPending && (
                <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden />
              )}
              {confirmMode === 'switch-and-migrate' ? 'Migrate now' : 'Switch now'}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  );
}
