import * as DocumentPicker from 'expo-document-picker';
import * as FileSystem from 'expo-file-system/legacy';
import { useSQLiteContext } from 'expo-sqlite';
import { useState } from 'react';
import { Alert, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { insertActivity } from '@/db/queries';
import { extractFile } from '@/extraction/extractActivity';

const FIT_EXTENSIONS  = ['.fit', '.fit.gz'];
const OTHER_EXTENSIONS = ['.gpx', '.tcx', '.gpx.gz', '.tcx.gz'];
const ALL_NATIVE_EXTENSIONS = [...FIT_EXTENSIONS, ...OTHER_EXTENSIONS];

type ImportState =
  | { status: 'idle' }
  | { status: 'loading'; msg: string }
  | { status: 'done'; title: string; id: string }
  | { status: 'error'; message: string };

export default function ImportScreen() {
  const db = useSQLiteContext();
  const [state, setState] = useState<ImportState>({ status: 'idle' });

  async function pickFile() {
    setState({ status: 'loading', msg: 'Picking file…' });
    try {
      const result = await DocumentPicker.getDocumentAsync({
        type: ['*/*'],
        copyToCacheDirectory: true,
      });
      if (result.canceled || !result.assets?.[0]) {
        setState({ status: 'idle' });
        return;
      }

      const asset = result.assets[0];
      const name  = asset.name ?? '';
      const uri   = asset.uri;
      const lower = name.toLowerCase();

      if (lower.endsWith('.json')) {
        await importBasJson(uri, name, db);
      } else if (ALL_NATIVE_EXTENSIONS.some(ext => lower.endsWith(ext))) {
        await importNativeFile(uri, name, db);
      } else {
        setState({ status: 'error', message: `Unsupported file type: ${name}` });
      }
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : String(e);
      setState({ status: 'error', message: msg });
    }
  }

  // ── BAS JSON import (no extraction needed) ──────────────────────────────────

  async function importBasJson(uri: string, name: string, dbCtx: typeof db) {
    setState({ status: 'loading', msg: 'Importing…' });
    const text   = await FileSystem.readAsStringAsync(uri);
    const detail = JSON.parse(text);

    if (!detail.id || !detail.started_at) {
      throw new Error('Not a valid BAS activity JSON (missing id or started_at)');
    }

    const hash    = detail.source_hash ?? `${detail.id}-${text.length}`;
    const origDir = `${FileSystem.documentDirectory}originals/`;
    await FileSystem.makeDirectoryAsync(origDir, { intermediates: true });
    const dest = `${origDir}${detail.id}.json`;
    await FileSystem.copyAsync({ from: uri, to: dest });

    await insertActivity(dbCtx, {
      id: detail.id,
      source_hash: hash,
      detail_json: text,
      timeseries_json: null,
      geojson: null,
      original_path: dest,
      origin: 'local',
    });

    setState({ status: 'done', title: detail.title ?? detail.id, id: detail.id });
  }

  // ── FIT / GPX / TCX import via Pyodide extraction ──────────────────────────

  async function importNativeFile(uri: string, name: string, dbCtx: typeof db) {
    setState({ status: 'loading', msg: 'Reading file…' });

    // Read the original file as base64 so we can (a) pass it to the WebView
    // and (b) copy it to permanent storage without a second read.
    const base64 = await FileSystem.readAsStringAsync(uri, {
      encoding: FileSystem.EncodingType.Base64,
    });

    // Fetch the bincio wheel here (React Native networking), not inside the
    // WebView. WKWebView blocks HTTP requests via ATS; RN native networking
    // allows local-network HTTP (NSAllowsLocalNetworking=true in Info.plist).
    const instanceUrl = await getInstanceUrl(dbCtx);
    setState({ status: 'loading', msg: 'Fetching Bincio engine…' });
    const wheelBase64 = await fetchWheelBase64(instanceUrl);

    const result = await extractFile(
      name,
      base64,
      wheelBase64,
      (msg) => setState({ status: 'loading', msg }),
    );

    setState({ status: 'loading', msg: 'Saving…' });

    // Copy original file to permanent storage (keeps original bytes for future re-extraction)
    const ext     = name.includes('.') ? name.slice(name.lastIndexOf('.')) : '';
    const origDir = `${FileSystem.documentDirectory}originals/`;
    await FileSystem.makeDirectoryAsync(origDir, { intermediates: true });
    const dest = `${origDir}${result.id}${ext}`;
    await FileSystem.copyAsync({ from: uri, to: dest });

    await insertActivity(dbCtx, {
      id: result.id,
      source_hash: result.sourceHash,
      detail_json: JSON.stringify(result.detail),
      timeseries_json: result.timeseries ? JSON.stringify(result.timeseries) : null,
      geojson: result.geojson ? JSON.stringify(result.geojson) : null,
      original_path: dest,
      origin: 'local',
    });

    const d = result.detail as Record<string, unknown>;
    setState({ status: 'done', title: (d.title as string) ?? result.id, id: result.id });
  }

  return (
    <ScrollView style={styles.container} contentContainerStyle={styles.content}>
      <Text style={styles.header}>Import</Text>

      <Text style={styles.body}>
        Import a FIT, GPX, or TCX file — extracted on your device, nothing uploaded.
        You can also import a pre-extracted BAS <Text style={styles.code}>.json</Text> file directly.
      </Text>

      <Pressable
        style={[styles.button, state.status === 'loading' && styles.buttonDisabled]}
        onPress={state.status !== 'loading' ? pickFile : undefined}
      >
        <Text style={styles.buttonText}>
          {state.status === 'loading' ? 'Working…' : '＋ Pick file'}
        </Text>
      </Pressable>

      {state.status === 'loading' && (
        <View style={styles.statusBox}>
          <Text style={styles.statusMsg}>{state.msg}</Text>
          <Text style={styles.statusHint}>
            First run downloads ~35 MB (Python runtime + packages). Subsequent runs are instant.
          </Text>
        </View>
      )}

      {state.status === 'done' && (
        <View style={styles.success}>
          <Text style={styles.successText}>✓ Imported: {state.title}</Text>
        </View>
      )}

      {state.status === 'error' && (
        <View style={styles.error}>
          <Text style={styles.errorText}>{state.message}</Text>
          <Pressable onPress={() => setState({ status: 'idle' })}>
            <Text style={styles.errorRetry}>Try another file</Text>
          </Pressable>
        </View>
      )}

      <View style={styles.divider} />

      <Text style={styles.sectionTitle}>Supported formats</Text>
      {([
        ['FIT',      'Garmin, Wahoo, Karoo native format'],
        ['GPX',      'Most GPS devices and apps'],
        ['TCX',      'Garmin Training Center'],
        ['BAS JSON', 'Pre-extracted Bincio format (instant)'],
      ] as [string, string][]).map(([fmt, desc]) => (
        <View key={fmt} style={styles.formatRow}>
          <Text style={styles.formatName}>{fmt}</Text>
          <Text style={styles.formatDesc}>{desc}</Text>
        </View>
      ))}

      <View style={styles.notice}>
        <Text style={styles.noticeText}>
          FIT/GPX/TCX extraction runs entirely on your device.
          A Bincio instance must be reachable on first run to download the extraction engine (~35 MB, then cached).
        </Text>
      </View>
    </ScrollView>
  );
}

// ── Helpers ─────────────────────────────────────────────────────────────────

async function getInstanceUrl(db: ReturnType<typeof useSQLiteContext>): Promise<string> {
  const row = db.getFirstSync<{ value: string }>(
    'SELECT value FROM settings WHERE key = ?',
    ['instance_url'],
  );
  return (row?.value ?? '').replace(/\/$/, '');
}

// In-memory cache so repeated imports in one session don't re-download the wheel.
let _cachedWheelBase64: string | null = null;

async function fetchWheelBase64(instanceUrl: string): Promise<string> {
  if (_cachedWheelBase64) return _cachedWheelBase64;

  const base = instanceUrl || 'https://bincio.org';

  // Ask the instance for the canonical wheel URL (handles both dev and prod layouts).
  let wheelUrl = `${base}/api/wheel/download`;
  try {
    const vr = await fetch(`${base}/api/wheel/version`, { signal: AbortSignal.timeout(5000) });
    if (vr.ok) {
      const d = await vr.json() as { api_url?: string; url?: string };
      const path = d.api_url ?? d.url ?? '/api/wheel/download';
      wheelUrl = path.startsWith('http') ? path : `${base}${path}`;
    }
  } catch {}

  // Fetch via React Native networking (supports local HTTP; WKWebView would block it).
  const resp = await fetch(wheelUrl);
  if (!resp.ok) throw new Error(`Could not download Bincio engine (${resp.status}). Is the instance running?`);
  const buf = await resp.arrayBuffer();
  _cachedWheelBase64 = arrayBufferToBase64(buf);
  return _cachedWheelBase64;
}

function arrayBufferToBase64(buf: ArrayBuffer): string {
  const bytes = new Uint8Array(buf);
  let binary = '';
  // Process in chunks to avoid spread-operator stack overflow on large arrays.
  const CHUNK = 8192;
  for (let i = 0; i < bytes.length; i += CHUNK) {
    binary += String.fromCharCode(...(bytes.subarray(i, i + CHUNK) as unknown as number[]));
  }
  return btoa(binary);
}

// ── Styles ───────────────────────────────────────────────────────────────────

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: '#09090b' },
  content:   { padding: 16, paddingTop: 60, paddingBottom: 40 },
  header:    { color: '#fff', fontSize: 22, fontWeight: '700', marginBottom: 12 },
  body:      { color: '#a1a1aa', fontSize: 14, lineHeight: 20, marginBottom: 24 },
  code:      { color: '#60a5fa', fontFamily: 'monospace' },
  button: {
    backgroundColor: '#2563eb', borderRadius: 10,
    paddingVertical: 14, alignItems: 'center', marginBottom: 16,
  },
  buttonDisabled: { opacity: 0.5 },
  buttonText:     { color: '#fff', fontWeight: '600', fontSize: 16 },
  statusBox: {
    backgroundColor: '#18181b', borderRadius: 8, borderWidth: 1,
    borderColor: '#27272a', padding: 14, marginBottom: 16, gap: 6,
  },
  statusMsg:  { color: '#60a5fa', fontSize: 14, textAlign: 'center' },
  statusHint: { color: '#52525b', fontSize: 12, textAlign: 'center', lineHeight: 16 },
  success: {
    backgroundColor: '#14532d', borderRadius: 8, padding: 12, marginBottom: 16,
  },
  successText: { color: '#86efac', fontSize: 14 },
  error: {
    backgroundColor: '#450a0a', borderRadius: 8, padding: 12, marginBottom: 16, gap: 8,
  },
  errorText:  { color: '#fca5a5', fontSize: 14 },
  errorRetry: { color: '#71717a', fontSize: 13, textDecorationLine: 'underline' },
  divider:      { height: 1, backgroundColor: '#27272a', marginVertical: 24 },
  sectionTitle: { color: '#a1a1aa', fontSize: 12, fontWeight: '600', marginBottom: 12, letterSpacing: 0.5 },
  formatRow:    { flexDirection: 'row', gap: 12, marginBottom: 10 },
  formatName:   { color: '#f4f4f5', fontSize: 13, fontWeight: '600', width: 72 },
  formatDesc:   { color: '#71717a', fontSize: 13, flex: 1 },
  notice: {
    marginTop: 8, backgroundColor: '#18181b',
    borderRadius: 8, padding: 12, borderWidth: 1, borderColor: '#27272a',
  },
  noticeText: { color: '#71717a', fontSize: 12, lineHeight: 18 },
});
