import { useState, useRef, type ReactNode } from 'react';
import { toast } from 'sonner';
import {
  Upload, Loader2, CheckCircle2, CheckCheck, AlertTriangle, Plus, FileText,
} from 'lucide-react';
import { api } from '@/api';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { SectionCard, CountPill, importErrorState, type SectionCardProps, type ImportErrorState } from './dataShared';

type CsvMapping = Record<string, string>;

interface CsvPreviewData {
  import_session_id?: string;
  headers?: string[];
  sampleRows?: Record<string, unknown>[];
  suggestedMapping?: Record<string, string>;
  fields?: Record<string, string>;
  rowCount?: number;
  errors?: { message?: string }[];
}

interface CsvRowDetail {
  field?: string;
  message?: string;
  value?: unknown;
}

interface CsvCommitRow {
  row: number;
  result?: string;
  provider_transaction_id?: string;
  message?: string;
  details?: CsvRowDetail[];
}

interface CsvCommitResult {
  imported?: number;
  skipped?: number;
  failed?: number;
  details?: CsvCommitRow[];
}

interface CsvPreviewState {
  status: 'idle' | 'loading' | 'ready' | 'error';
  data: CsvPreviewData | null;
  error: ImportErrorState | null;
}

interface CsvCommitState {
  status: 'idle' | 'loading' | 'done' | 'error';
  result: CsvCommitResult | null;
  error: ImportErrorState | null;
}

const CSV_MAPPING_FIELDS = [
  'posted_date',
  'amount',
  'debit_amount',
  'credit_amount',
  'description',
  'payee',
  'memo',
  'category',
  'account',
  'transaction_id',
  'transaction_type',
  'currency',
  'transacted_at',
];

function compactMapping(mapping: CsvMapping | null | undefined): CsvMapping {
  return Object.fromEntries(
    Object.entries(mapping || {}).filter(([, value]) => value),
  ) as CsvMapping;
}

function canCommitCsvMapping(mapping: CsvMapping | null | undefined): boolean {
  return !!mapping?.posted_date && !!(mapping?.amount || mapping?.debit_amount || mapping?.credit_amount);
}

const CSV_IMPORT_STEPS = ['Upload', 'Preview', 'Map', 'Commit', 'Results'];

function csvImportStepIndex(preview: CsvPreviewState, mapping: CsvMapping, commitState: CsvCommitState): number {
  if (commitState.status === 'done') return 4;
  if (commitState.status === 'loading') return 3;
  if (preview.status === 'ready') return canCommitCsvMapping(mapping) ? 3 : 2;
  if (preview.status === 'loading' || preview.status === 'error') return 1;
  return 0;
}

function CsvImportStepper({ activeIndex }: { activeIndex: number }) {
  return (
    <div className="grid grid-cols-2 gap-2 sm:grid-cols-5">
      {CSV_IMPORT_STEPS.map((step, index) => {
        const complete = index < activeIndex;
        const active = index === activeIndex;
        return (
          <div
            key={step}
            className={cn(
              'flex items-center gap-2 rounded-lg border px-3 py-2 text-xs transition-colors',
              complete && 'border-emerald-500/30 bg-emerald-500/5 text-emerald-600',
              active && 'border-primary/40 bg-primary/5 text-foreground',
              !complete && !active && 'border-border/60 bg-muted/20 text-muted-foreground',
            )}
          >
            <span className={cn(
              'flex h-5 w-5 shrink-0 items-center justify-center rounded-full border text-[10px] font-semibold tabular-nums',
              complete && 'border-emerald-500/40 bg-emerald-500/10 text-emerald-600',
              active && 'border-primary/50 bg-primary/10 text-primary',
              !complete && !active && 'border-border text-muted-foreground',
            )}>
              {complete ? <CheckCircle2 className="h-3 w-3" /> : index + 1}
            </span>
            <span className="truncate font-medium">{step}</span>
          </div>
        );
      })}
    </div>
  );
}

function csvFieldRequirement(field: string, mapping: CsvMapping): string {
  if (field === 'posted_date') return 'Required';
  if (['amount', 'debit_amount', 'credit_amount'].includes(field)) {
    return canCommitCsvMapping({ ...mapping, posted_date: mapping?.posted_date || '__date__' })
      ? 'Amount source'
      : 'One required';
  }
  return 'Optional';
}

function csvFieldSamples(preview: CsvPreviewData | null | undefined, header: string): string[] {
  if (!header) return [];
  const values: string[] = [];
  for (const row of preview?.sampleRows || []) {
    const value = String(row?.[header] || '').trim();
    if (value && !values.includes(value)) values.push(value);
    if (values.length >= 3) break;
  }
  return values;
}

interface CsvMappingRowProps {
  field: string;
  label: string;
  preview: CsvPreviewData | null | undefined;
  mapping: CsvMapping;
  onChange: (field: string, header: string) => void;
  disabled?: boolean;
}

function CsvMappingRow({ field, label, preview, mapping, onChange, disabled = false }: CsvMappingRowProps) {
  const headers = preview?.headers || [];
  const suggested = preview?.suggestedMapping?.[field] || '';
  const current = mapping[field] || '';
  const used = new Set(Object.entries(mapping)
    .filter(([key, value]) => key !== field && value)
    .map(([, value]) => value));
  const requirement = csvFieldRequirement(field, mapping);
  const missingRequired = (requirement === 'Required' || requirement === 'One required') && !current;
  const samples = csvFieldSamples(preview, current);
  const suggestedAvailable = suggested && suggested !== current && !used.has(suggested);

  return (
    <div className={cn(
      'grid gap-3 border-b border-border/40 px-4 py-3 last:border-b-0 lg:grid-cols-[minmax(150px,0.8fr)_minmax(220px,1fr)_minmax(180px,1fr)]',
      missingRequired && 'bg-destructive/5',
    )}>
      <div className="min-w-0">
        <div className="flex items-center gap-2">
          <p className="truncate text-sm font-medium">{label}</p>
          <span className={cn(
            'rounded-full border px-2 py-0.5 text-[10px] font-semibold uppercase',
            requirement === 'Required' || requirement === 'One required'
              ? 'border-destructive/30 bg-destructive/10 text-destructive'
              : requirement === 'Amount source'
                ? 'border-amber-500/30 bg-amber-500/10 text-amber-600 dark:text-amber-400'
                : 'border-border/60 bg-muted/30 text-muted-foreground',
          )}>
            {requirement}
          </span>
        </div>
        <p className="mt-1 font-mono text-[11px] text-muted-foreground">{field}</p>
      </div>

      <div className="space-y-1.5">
        <select
          value={current}
          onChange={e => onChange(field, e.target.value)}
          disabled={disabled}
          className={cn(
            'h-9 w-full rounded-md border bg-background px-2 text-sm focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-60',
            missingRequired ? 'border-destructive/50' : 'border-input',
          )}
        >
          <option value="">Not mapped</option>
          {headers.map(header => (
            <option key={header} value={header} disabled={used.has(header)}>
              {header}{used.has(header) ? ' (assigned)' : ''}
            </option>
          ))}
        </select>
        <div className="flex min-h-5 flex-wrap items-center gap-1.5">
          {current && current === suggested && (
            <span className="text-[11px] font-medium text-emerald-600">Suggested match</span>
          )}
          {suggestedAvailable && !disabled && (
            <button
              type="button"
              onClick={() => onChange(field, suggested)}
              className="rounded-full border border-border/70 px-2 py-0.5 text-[11px] text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
            >
              Use {suggested}
            </button>
          )}
          {missingRequired && (
            <span className="text-[11px] text-destructive">Needs a column</span>
          )}
        </div>
      </div>

      <div className="min-w-0">
        {samples.length > 0 ? (
          <div className="flex flex-wrap gap-1.5">
            {samples.map(value => (
              <span key={value} className="max-w-full truncate rounded border border-border/50 bg-muted/25 px-2 py-1 text-[11px] text-muted-foreground">
                {value}
              </span>
            ))}
          </div>
        ) : (
          <p className="text-xs text-muted-foreground">
            {current ? 'No sample values' : 'Map a column to preview values'}
          </p>
        )}
      </div>
    </div>
  );
}

interface CsvMappingReviewProps {
  preview: CsvPreviewData | null | undefined;
  fields: Record<string, string>;
  mapping: CsvMapping;
  onMappingChange: (field: string, header: string) => void;
  onUseSuggested: () => void;
  onClearMapping: () => void;
  disabled?: boolean;
}

function CsvMappingReview({ preview, fields, mapping, onMappingChange, onUseSuggested, onClearMapping, disabled = false }: CsvMappingReviewProps) {
  const mappingFields = CSV_MAPPING_FIELDS.filter(field => fields[field]);
  const mappedCount = mappingFields.filter(field => mapping[field]).length;
  const hasSuggestedMapping = Object.values(preview?.suggestedMapping || {}).some(Boolean);
  const missingRequired = [
    !mapping.posted_date ? 'Posted date' : null,
    !(mapping.amount || mapping.debit_amount || mapping.credit_amount) ? 'Amount' : null,
  ].filter(Boolean);

  return (
    <div className="overflow-hidden rounded-lg border border-border/60">
      <div className="flex flex-wrap items-center justify-between gap-3 border-b border-border/50 bg-muted/25 px-4 py-3">
        <div>
          <p className="text-sm font-medium">Column mapping</p>
          <p className="mt-0.5 text-xs text-muted-foreground">
            {mappedCount} of {mappingFields.length} fields mapped
            {missingRequired.length > 0 && ` · missing ${missingRequired.join(' and ')}`}
          </p>
        </div>
        <div className="flex gap-2">
          <Button size="sm" variant="outline" type="button" onClick={onUseSuggested} disabled={disabled || !hasSuggestedMapping}>
            Use Suggested
          </Button>
          <Button size="sm" variant="ghost" type="button" onClick={onClearMapping} disabled={disabled}>
            Clear Mapping
          </Button>
        </div>
      </div>

      <div className="hidden border-b border-border/50 bg-muted/10 px-4 py-2 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground lg:grid lg:grid-cols-[minmax(150px,0.8fr)_minmax(220px,1fr)_minmax(180px,1fr)]">
        <span>Field</span>
        <span>CSV Column</span>
        <span>Sample Values</span>
      </div>

      <div>
        {mappingFields.map(field => (
          <CsvMappingRow
            key={field}
            field={field}
            label={fields[field] ?? field}
            preview={preview}
            mapping={mapping}
            onChange={onMappingChange}
            disabled={disabled}
          />
        ))}
      </div>
    </div>
  );
}

function CsvSampleTable({ preview }: { preview: CsvPreviewData | null | undefined }) {
  const headers = preview?.headers || [];
  const sampleRows = preview?.sampleRows || [];
  const visibleHeaders = headers.slice(0, 8);
  const hiddenCount = Math.max(0, headers.length - visibleHeaders.length);

  if (sampleRows.length === 0) {
    return <p className="py-4 text-center text-sm text-muted-foreground">No sample rows found.</p>;
  }

  return (
    <div className="overflow-x-auto rounded-lg border border-border/60">
      <table className="w-full text-xs">
        <thead>
          <tr className="border-b border-border/50 bg-muted/40 text-muted-foreground">
            {visibleHeaders.map(header => (
              <th key={header} className="px-3 py-2 text-left font-medium whitespace-nowrap">{header}</th>
            ))}
            {hiddenCount > 0 && (
              <th className="px-3 py-2 text-left font-medium whitespace-nowrap">+{hiddenCount}</th>
            )}
          </tr>
        </thead>
        <tbody className="divide-y divide-border/30">
          {sampleRows.map((row, index) => (
            <tr key={index} className="hover:bg-muted/20">
              {visibleHeaders.map(header => (
                <td key={header} className="max-w-48 truncate px-3 py-2 text-muted-foreground">
                  {(row[header] as ReactNode) || '—'}
                </td>
              ))}
              {hiddenCount > 0 && (
                <td className="px-3 py-2 text-muted-foreground">more columns</td>
              )}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function formatCsvRowDetail(detail: CsvRowDetail | null | undefined): string {
  if (!detail) return '';
  const field = detail.field ? `${detail.field}: ` : '';
  return `${field}${detail.message || detail.value || JSON.stringify(detail)}`;
}

export default function ImportTransactionCsvSection({ onHistoryRefresh, cardProps = {} }: {
  onHistoryRefresh?: () => void;
  cardProps?: Partial<SectionCardProps>;
}) {
  const fileRef = useRef<HTMLInputElement>(null);
  const [file, setFile] = useState<File | null>(null);
  const [preview, setPreview] = useState<CsvPreviewState>({ status: 'idle', data: null, error: null });
  const [mapping, setMapping] = useState<CsvMapping>({});
  const [commitState, setCommitState] = useState<CsvCommitState>({ status: 'idle', result: null, error: null });

  const reset = () => {
    setFile(null);
    setPreview({ status: 'idle', data: null, error: null });
    setMapping({});
    setCommitState({ status: 'idle', result: null, error: null });
    if (fileRef.current) fileRef.current.value = '';
  };

  const handleMappingChange = (field: string, header: string) => {
    if (commitState.status === 'done') return;
    setMapping(prev => {
      const next = { ...prev };
      if (header) next[field] = header;
      else delete next[field];
      return next;
    });
    setCommitState({ status: 'idle', result: null, error: null });
  };

  const handlePreview = async () => {
    if (!file) {
      toast.error('Choose a CSV file first.');
      return;
    }
    setPreview({ status: 'loading', data: null, error: null });
    setMapping({});
    setCommitState({ status: 'idle', result: null, error: null });
    try {
      const data = await api.previewCsvTransactionImport(file) as CsvPreviewData;
      setPreview({ status: 'ready', data, error: null });
      setMapping(compactMapping(data.suggestedMapping || {}));
      toast.success('CSV preview ready.');
    } catch (err) {
      const errorState = importErrorState(err, 'CSV preview failed.');
      setPreview({ status: 'error', data: null, error: errorState });
      toast.error(errorState.message || 'CSV preview failed.');
    }
  };

  const handleCommit = async () => {
    if (!preview.data?.import_session_id || !canCommitCsvMapping(mapping)) return;
    setCommitState({ status: 'loading', result: null, error: null });
    try {
      const result = await api.commitCsvTransactionImport({
        import_session_id: preview.data.import_session_id,
        mapping: compactMapping(mapping),
      }) as CsvCommitResult;
      setCommitState({ status: 'done', result, error: null });
      toast.success(`CSV imported — ${result.imported} imported, ${result.skipped} skipped.`);
      onHistoryRefresh?.();
    } catch (err) {
      const errorState = importErrorState(err, 'CSV import failed.');
      setCommitState({ status: 'error', result: null, error: errorState });
      toast.error(errorState.message || 'CSV import failed.');
    }
  };

  const applySuggestedMapping = () => {
    if (commitState.status === 'done') return;
    setMapping(compactMapping(preview.data?.suggestedMapping || {}));
    setCommitState({ status: 'idle', result: null, error: null });
  };

  const clearMapping = () => {
    if (commitState.status === 'done') return;
    setMapping({});
    setCommitState({ status: 'idle', result: null, error: null });
  };

  const fields = preview.data?.fields || {};
  const canCommit = preview.status === 'ready'
    && !!preview.data?.import_session_id
    && canCommitCsvMapping(mapping)
    && commitState.status !== 'loading'
    && commitState.status !== 'done';
  const activeStep = csvImportStepIndex(preview, mapping, commitState);
  const failedRows = (commitState.result?.details || []).filter(d => d.result === 'failed');
  const skippedRows = (commitState.result?.details || []).filter(d => d.result === 'skipped_duplicate');

  return (
    <SectionCard
      title="Import Transaction CSV"
      subtitle="Upload a bank or credit-card CSV, map its columns, then save normalized transactions for matching later."
      {...cardProps}
    >
      <div className="px-6 py-5 space-y-5">
        <div className="rounded-lg border border-border/60 bg-muted/25 p-4">
          <div className="flex items-start gap-3">
            <FileText className="mt-0.5 h-5 w-5 text-primary shrink-0" />
            <div>
              <p className="text-sm font-medium">Import transaction rows from CSV.</p>
              <p className="mt-1 text-xs text-muted-foreground">
                This importer creates shared transaction records only. It does not match transactions to bills yet.
              </p>
            </div>
          </div>
        </div>

        <CsvImportStepper activeIndex={activeStep} />

        <div className="rounded-lg border border-border/60 bg-background/50 p-4">
          <div className="flex flex-col gap-3 sm:flex-row sm:items-end">
            <label className="flex-1 space-y-1.5">
              <span className="text-xs font-medium text-muted-foreground">CSV file</span>
              <Input
                ref={fileRef}
                type="file"
                accept=".csv,text/csv"
                onChange={e => {
                  setFile(e.target.files?.[0] || null);
                  setPreview({ status: 'idle', data: null, error: null });
                  setMapping({});
                  setCommitState({ status: 'idle', result: null, error: null });
                }}
              />
            </label>
            <div className="flex gap-2">
              <Button size="sm" variant="outline" type="button" onClick={reset}>
                Clear
              </Button>
              <Button size="sm" type="button" disabled={!file || preview.status === 'loading'} onClick={handlePreview}>
                {preview.status === 'loading'
                  ? <><Loader2 className="h-3.5 w-3.5 mr-1.5 animate-spin" />Previewing…</>
                  : <><Upload className="h-3.5 w-3.5 mr-1.5" />Preview</>}
              </Button>
            </div>
          </div>
        </div>

        {preview.status === 'error' && (
          <div className="rounded-lg border border-destructive/30 bg-destructive/10 p-3 text-sm text-destructive">
            <AlertTriangle className="mr-1.5 inline h-4 w-4" />
            {preview.error?.message || 'CSV preview failed.'}
            {(preview.error?.details?.length ?? 0) > 0 && (
              <ul className="mt-2 list-disc pl-5 text-xs">
                {preview.error?.details.map((d, i) => {
                  const dd = d as { message?: string };
                  return <li key={i}>{dd.message || JSON.stringify(d)}</li>;
                })}
              </ul>
            )}
          </div>
        )}

        {preview.status === 'ready' && preview.data && (
          <div className="space-y-5">
            <div className="rounded-lg border border-border bg-muted/30 p-4 space-y-4">
              <div className="flex flex-wrap items-start justify-between gap-3">
                <div>
                  <p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">CSV Preview</p>
                  <p className="mt-1 text-sm font-medium">{file?.name || 'Transaction CSV'}</p>
                </div>
                <div className="flex flex-wrap gap-2">
                  <CountPill label="Rows" value={preview.data.rowCount} />
                  <CountPill label="Columns" value={preview.data.headers?.length || 0} />
                  <CountPill label="Issues" value={preview.data.errors?.length || 0} />
                </div>
              </div>

              {(preview.data.errors?.length ?? 0) > 0 && (
                <div className="rounded-lg border border-amber-500/30 bg-amber-500/5 p-3">
                  <p className="text-xs font-semibold uppercase tracking-widest text-amber-600 dark:text-amber-400">Review mapping</p>
                  <ul className="mt-2 space-y-1 text-xs text-amber-700 dark:text-amber-300">
                    {preview.data.errors?.map((issue, i) => (
                      <li key={i} className="flex items-start gap-1.5">
                        <AlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0" />
                        <span>{issue.message || JSON.stringify(issue)}</span>
                      </li>
                    ))}
                  </ul>
                </div>
              )}

              <CsvSampleTable preview={preview.data} />
            </div>

            <CsvMappingReview
              preview={preview.data}
              fields={fields}
              mapping={mapping}
              onMappingChange={handleMappingChange}
              onUseSuggested={applySuggestedMapping}
              onClearMapping={clearMapping}
              disabled={commitState.status === 'done'}
            />

            <div className="rounded-lg border border-border/60 bg-muted/25 px-4 py-3 flex items-center justify-between gap-3 flex-wrap">
              <div>
                <p className="text-sm font-medium">
                  {canCommitCsvMapping(mapping) ? 'Ready to commit' : 'Mapping incomplete'}
                </p>
                <p className="mt-0.5 text-xs text-muted-foreground">
                  Duplicates are skipped using a CSV transaction ID when available, otherwise a stable row hash.
                </p>
              </div>
              {commitState.status === 'done' ? (
                <Button size="sm" variant="outline" type="button" onClick={reset}>
                  <Plus className="h-3.5 w-3.5 mr-1.5" />New CSV import
                </Button>
              ) : (
                <Button size="sm" type="button" disabled={!canCommit} onClick={handleCommit}>
                  {commitState.status === 'loading'
                    ? <><Loader2 className="h-3.5 w-3.5 mr-1.5 animate-spin" />Importing…</>
                    : <><CheckCheck className="h-3.5 w-3.5 mr-1.5" />Commit Import</>}
                </Button>
              )}
            </div>
          </div>
        )}

        {commitState.status === 'done' && commitState.result && (
          <div className="rounded-lg border border-emerald-500/30 bg-emerald-500/5 p-4">
            <div className="flex items-center gap-2">
              <CheckCircle2 className="h-5 w-5 text-emerald-500" />
              <p className="text-sm font-medium text-emerald-600">CSV transaction import complete</p>
            </div>
            <div className="mt-3 grid grid-cols-3 gap-2 text-xs">
              <CountPill label="Imported" value={commitState.result.imported} />
              <CountPill label="Skipped" value={commitState.result.skipped} />
              <CountPill label="Failed" value={commitState.result.failed} />
            </div>
            {skippedRows.length > 0 && (
              <div className="mt-3 text-xs text-muted-foreground">
                <p className="font-medium text-foreground">Skipped duplicates ({skippedRows.length})</p>
                <ul className="mt-1 max-h-44 space-y-1 overflow-y-auto rounded-md border border-border/50 bg-background/40 p-2">
                  {skippedRows.map(row => (
                    <li key={row.row} className="break-all">
                      Row {row.row}: {row.provider_transaction_id}
                    </li>
                  ))}
                </ul>
              </div>
            )}
            {failedRows.length > 0 && (
              <div className="mt-3 text-xs text-destructive">
                <p className="font-medium">Failed rows ({failedRows.length})</p>
                <ul className="mt-1 max-h-64 space-y-2 overflow-y-auto rounded-md border border-destructive/20 bg-background/40 p-2">
                  {failedRows.map((row, index) => (
                    <li key={`${row.row}-${index}`} className="rounded border border-destructive/10 bg-destructive/5 px-2 py-1.5">
                      <p>Row {row.row}: {row.message}</p>
                      {(row.details?.length ?? 0) > 0 && (
                        <ul className="mt-1 space-y-0.5 pl-3 text-destructive/90">
                          {row.details?.map((detail, detailIndex) => (
                            <li key={detailIndex}>{formatCsvRowDetail(detail)}</li>
                          ))}
                        </ul>
                      )}
                    </li>
                  ))}
                </ul>
              </div>
            )}
          </div>
        )}

        {commitState.status === 'error' && (
          <div className="rounded-lg border border-destructive/30 bg-destructive/10 p-3 text-sm text-destructive">
            <AlertTriangle className="mr-1.5 inline h-4 w-4" />
            {commitState.error?.message || 'CSV import failed.'}
            {(commitState.error?.details?.length ?? 0) > 0 && (
              <ul className="mt-2 list-disc pl-5 text-xs">
                {commitState.error?.details.map((d, i) => {
                  const dd = d as { message?: string };
                  return <li key={i}>{dd.message || JSON.stringify(d)}</li>;
                })}
              </ul>
            )}
          </div>
        )}
      </div>
    </SectionCard>
  );
}
