import * as FileSystem from 'expo-file-system';
import * as Sharing from 'expo-sharing';
import { zip } from 'react-native-zip-archive';
import { Platform } from 'react-native';

import { APP_VERSION } from '../../constants/layout';
import type { PhotoRecord } from '../photoStorage/types';
import { loadPhotoRecords } from '../photoStorage/photoStorageService';

declare const __DEV__: boolean;

function isDevRuntime() {
  return typeof __DEV__ !== 'undefined' && __DEV__;
}

function logDev(message: string, details: Record<string, unknown>) {
  if (isDevRuntime()) {
    console.log(message, details);
  }
}

function warnDev(message: string, details: Record<string, unknown>) {
  if (isDevRuntime()) {
    console.warn(message, details);
  }
}

const gardenStorageDir = `${FileSystem.documentDirectory || ''}pixiesprout-garden/`;
const photosDir = `${FileSystem.documentDirectory || ''}pixiesprout-photos/`;
// Use documentDirectory as PRIMARY for the export temp dir — cacheDirectory is
// unreliable on Android (can be null / permission issues) and causes zip() to
// produce a zero-byte archive. Fall back to cacheDirectory only if needed.
const exportTempDir = `${FileSystem.documentDirectory || FileSystem.cacheDirectory || ''}pixiesprout-export/`;

type ExportManifest = {
  exportVersion: string;
  exportedAt: string;
  appVersion: string;
  plantCount: number;
  memoryCount: number;
  investigationCount: number;
  intelligenceProfileCount: number;
  photoCount: number;
  includedPhotos: number;
  missingPhotos: number;
};

type ExportResult = {
  success: boolean;
  archivePath?: string;
  error?: string;
  manifest?: ExportManifest;
};

export type ExportPhotoQuality = 'original' | 'compressed';
export type ExportProgress = { phase: 'photos' | 'archive'; done: number; total: number };
export type ExportOptions = {
  photoQuality?: ExportPhotoQuality;
  onProgress?: (p: ExportProgress) => void;
};

async function readJsonSafe<T>(path: string, fallback: T): Promise<T> {
  try {
    const info = await FileSystem.getInfoAsync(path);
    if (!info.exists) return fallback;
    return JSON.parse(await FileSystem.readAsStringAsync(path)) as T;
  } catch {
    return fallback;
  }
}

async function ensureDir(dir: string) {
  const info = await FileSystem.getInfoAsync(dir);
  if (!info.exists) {
    await FileSystem.makeDirectoryAsync(dir, { intermediates: true });
  }
}

async function copyFileIfExists(source: string, dest: string): Promise<boolean> {
  try {
    const info = await FileSystem.getInfoAsync(source);
    if (!info.exists) return false;
    await FileSystem.copyAsync({ from: source, to: dest });
    return true;
  } catch {
    return false;
  }
}

/**
 * Pick the best available photo source for export.
 * - 'original': prefer the full-res original; fall back to compressed/thumbnail.
 * - 'compressed': prefer the 800px compressed copy (much smaller/faster); fall
 *   back to original if no compressed copy exists yet.
 */
function pickPhotoSource(record: PhotoRecord, quality: ExportPhotoQuality): string | undefined {
  if (quality === 'compressed') {
    if (record.compressedUri) return record.compressedUri;
    if (record.originalUri) return record.originalUri;
    return record.thumbnailUri;
  }
  if (record.originalUri) return record.originalUri;
  if (record.compressedUri) return record.compressedUri;
  return record.thumbnailUri;
}

/** Copy a list of {source, dest} pairs concurrently, reporting progress. */
async function copyFilesConcurrent(
  jobs: Array<{ source: string; dest: string }>,
  onProgress?: (done: number, total: number) => void
): Promise<number> {
  const total = jobs.length;
  let done = 0;
  let copied = 0;
  const CONCURRENCY = 4;
  let idx = 0;
  const worker = async () => {
    while (idx < total) {
      const job = jobs[idx++];
      const ok = await copyFileIfExists(job.source, job.dest);
      if (ok) copied++;
      done++;
      onProgress?.(done, total);
    }
  };
  const workers = Array.from({ length: Math.min(CONCURRENCY, total) }, () => worker());
  await Promise.all(workers);
  return copied;
}

export async function generateExport(options: ExportOptions = {}): Promise<ExportResult> {
  const { photoQuality = 'original', onProgress } = options;
  try {
    // Clean previous export temp dir
    const existingDir = await FileSystem.getInfoAsync(exportTempDir);
    if (existingDir.exists) {
      await FileSystem.deleteAsync(exportTempDir, { idempotent: true });
    }
    await ensureDir(exportTempDir);

    const exportPhotosDir = `${exportTempDir}photos/`;
    await ensureDir(exportPhotosDir);
    // Create photo subdirectories
    for (const subdir of ['plant_id', 'profile', 'environment', 'setup', 'progress', 'memory', 'investigation', 'kit', 'wishlist']) {
      await ensureDir(`${exportPhotosDir}${subdir}/`);
    }

    logDev('[Export] reading data sources', {});

    // Read all data sources
    const savedPlants = await readJsonSafe<{ savedPlants: unknown[] }>(
      `${gardenStorageDir}saved-plants.json`,
      { savedPlants: [] }
    );
    const infrastructure = await readJsonSafe<Record<string, unknown>>(
      `${gardenStorageDir}garden-infrastructure.json`,
      {}
    );
    const intelligenceProfiles = await readJsonSafe<Record<string, unknown>>(
      `${gardenStorageDir}plant-intelligence-profiles.json`,
      {}
    );
    const photoRecords = await loadPhotoRecords();
    // Real investigations store (Record<string, Investigation>) — the source of
    // truth for cases (points/progressUpdates/treatmentPlan/collapsedPoints).
    // Previously only ai_finding events were exported, losing real cases.
    const investigationsStore = await readJsonSafe<Record<string, unknown>>(
      `${gardenStorageDir}investigations.json`,
      {}
    );

    // Extract plant events (memories) from saved plants
    const plants = Array.isArray(savedPlants.savedPlants) ? savedPlants.savedPlants : [];
    const allEvents: unknown[] = [];
    for (const plant of plants as Record<string, unknown>[]) {
      const events = (plant as Record<string, unknown>).events || (plant as Record<string, unknown>).plantEvents;
      if (Array.isArray(events)) {
        allEvents.push(...events);
      }
    }

    // Copy photos (parallel, quality-aware)
    let includedPhotos = 0;
    let missingPhotos = 0;
    const records = Object.values(photoRecords);
    const photoJobs: Array<{ source: string; dest: string }> = [];

    for (const record of records) {
      const photoRecord = record as PhotoRecord;
      const photoType = photoRecord.photoType || 'memory';
      const filename = `${photoRecord.photoId}.jpg`;
      const source = pickPhotoSource(photoRecord, photoQuality);
      if (source) {
        photoJobs.push({ source, dest: `${exportPhotosDir}${photoType}/${filename}` });
      }
      if (photoRecord.thumbnailUri) {
        photoJobs.push({
          source: photoRecord.thumbnailUri,
          dest: `${exportPhotosDir}${photoType}/${photoRecord.photoId}_thumb.jpg`,
        });
      }
      // Also copy the compressed copy so a phone migration restores the full
      // photo set (original + compressed + thumbnail) — otherwise compressed
      // photos come back with a stale absolute URI pointing at the old device.
      if (photoRecord.compressedUri) {
        photoJobs.push({
          source: photoRecord.compressedUri,
          dest: `${exportPhotosDir}${photoType}/${photoRecord.photoId}_compressed.jpg`,
        });
      }
    }

    // Also copy photos referenced in plant data but not in photo records
    // (environment photos, setup photos stored as GardenContextPhoto)
    const rooms = Array.isArray(infrastructure.rooms) ? infrastructure.rooms : [];
    for (const room of rooms as Record<string, unknown>[]) {
      const envPhotos = room.environmentPhotos;
      if (Array.isArray(envPhotos)) {
        for (const photo of envPhotos as Record<string, unknown>[]) {
          if (photo.uri && typeof photo.uri === 'string') {
            const filename = `env_${photo.id || Date.now()}.jpg`;
            photoJobs.push({ source: photo.uri, dest: `${exportPhotosDir}environment/${filename}` });
          }
        }
      }
    }

    const setups = Array.isArray(infrastructure.plantSetups) ? infrastructure.plantSetups : [];
    for (const setup of setups as Record<string, unknown>[]) {
      const setupPhotos = setup.setupPhotos;
      if (Array.isArray(setupPhotos)) {
        for (const photo of setupPhotos as Record<string, unknown>[]) {
          if (photo.uri && typeof photo.uri === 'string') {
            const filename = `setup_${photo.id || Date.now()}.jpg`;
            photoJobs.push({ source: photo.uri, dest: `${exportPhotosDir}setup/${filename}` });
          }
        }
      }
    }

    const photoTotal = photoJobs.length;
    includedPhotos = await copyFilesConcurrent(photoJobs, (done) => {
      onProgress?.({ phase: 'photos', done, total: photoTotal });
    });
    missingPhotos = photoTotal - includedPhotos;

    // Write JSON files
    await FileSystem.writeAsStringAsync(
      `${exportTempDir}plants.json`,
      JSON.stringify(plants, null, 2)
    );
    await FileSystem.writeAsStringAsync(
      `${exportTempDir}memories.json`,
      JSON.stringify(allEvents, null, 2)
    );
    await FileSystem.writeAsStringAsync(
      `${exportTempDir}investigations.json`,
      JSON.stringify(investigationsStore, null, 2)
    );
    await FileSystem.writeAsStringAsync(
      `${exportTempDir}intelligence-profiles.json`,
      JSON.stringify(intelligenceProfiles, null, 2)
    );
    await FileSystem.writeAsStringAsync(
      `${exportTempDir}infrastructure.json`,
      JSON.stringify(infrastructure, null, 2)
    );

    // Additional data stores so a phone migration restores the full app state
    // (settings, wishlist, care-task actions, notification states). These were
    // previously NOT exported — a migration silently lost them.
    const appSettings = await readJsonSafe<unknown>(
      `${gardenStorageDir}app-settings.json`,
      null
    );
    const wishlistItems = await readJsonSafe<unknown>(
      `${gardenStorageDir}wishlist-items.json`,
      null
    );
    const careTaskActions = await readJsonSafe<unknown>(
      `${gardenStorageDir}care-task-actions.json`,
      null
    );
    const notificationStates = await readJsonSafe<unknown>(
      `${gardenStorageDir}notification-states.json`,
      null
    );
    if (appSettings !== null) {
      await FileSystem.writeAsStringAsync(`${exportTempDir}app-settings.json`, JSON.stringify(appSettings, null, 2));
    }
    if (wishlistItems !== null) {
      await FileSystem.writeAsStringAsync(`${exportTempDir}wishlist-items.json`, JSON.stringify(wishlistItems, null, 2));
    }
    if (careTaskActions !== null) {
      await FileSystem.writeAsStringAsync(`${exportTempDir}care-task-actions.json`, JSON.stringify(careTaskActions, null, 2));
    }
    if (notificationStates !== null) {
      await FileSystem.writeAsStringAsync(`${exportTempDir}notification-states.json`, JSON.stringify(notificationStates, null, 2));
    }

    const photoRecordsExport: Record<string, unknown> = {};
    for (const [id, record] of Object.entries(photoRecords)) {
      const r = record as PhotoRecord;
      photoRecordsExport[id] = {
        ...r,
        // Include metadata but not the actual file data (photos are in the photos/ dir)
        originalUri: r.originalUri ? `photos/${r.photoType}/${r.photoId}.jpg` : undefined,
        thumbnailUri: r.thumbnailUri ? `photos/${r.photoType}/${r.photoId}_thumb.jpg` : undefined,
        compressedUri: r.compressedUri ? `photos/${r.photoType}/${r.photoId}_compressed.jpg` : undefined,
      };
    }
    await FileSystem.writeAsStringAsync(
      `${exportTempDir}photo-metadata.json`,
      JSON.stringify(photoRecordsExport, null, 2)
    );

    // Write manifest
    const manifest: ExportManifest = {
      exportVersion: '1.0',
      exportedAt: new Date().toISOString(),
      appVersion: APP_VERSION,
      plantCount: plants.length,
      memoryCount: allEvents.length,
      investigationCount: Object.keys(investigationsStore).length,
      intelligenceProfileCount: Object.keys(intelligenceProfiles).length,
      photoCount: records.length,
      includedPhotos,
      missingPhotos,
    };
    await FileSystem.writeAsStringAsync(
      `${exportTempDir}manifest.json`,
      JSON.stringify(manifest, null, 2)
    );

    logDev('[Export] data written, creating archive', { includedPhotos, missingPhotos });

    // Create ZIP archive
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
    const archiveName = `pixiesprout-export-${timestamp}.zip`;
    onProgress?.({ phase: 'archive', done: 0, total: 1 });
    const archivePath = await zip(exportTempDir, `${FileSystem.documentDirectory || FileSystem.cacheDirectory || ''}${archiveName}`);
    onProgress?.({ phase: 'archive', done: 1, total: 1 });

    logDev('[Export] archive created', { archivePath, manifest });

    return {
      success: true,
      archivePath,
      manifest,
    };
  } catch (error) {
    const msg = error instanceof Error ? error.message : 'unknown error';
    warnDev('[Export] failed', { error: msg });
    return { success: false, error: msg };
  }
}

export async function shareExport(archivePath: string): Promise<boolean> {
  try {
    const isAvailable = await Sharing.isAvailableAsync();
    if (!isAvailable) {
      warnDev('[Export] sharing not available on this device', {});
      return false;
    }
    await Sharing.shareAsync(archivePath, {
      mimeType: 'application/zip',
      dialogTitle: 'Save your PixieSprout plant journal',
      UTI: 'public.zip-archive',
    });
    return true;
  } catch (error) {
    const msg = error instanceof Error ? error.message : 'unknown error';
    warnDev('[Export] sharing failed', { error: msg });
    return false;
  }
}

/**
 * Save an exported ZIP to a user-chosen folder using Android's Storage Access
 * Framework (expo-file-system, already in the native build). This opens a real
 * native "save to folder" picker so the user can actually grab the file — unlike
 * expo-sharing's share sheet, which may not surface a dialog on some devices.
 * Falls back to expo-sharing when SAF isn't available (iOS / older Android).
 */
export async function saveExportToDisk(archivePath: string, suggestedName = 'pixiesprout-export.zip'): Promise<{ saved: boolean; directoryUri?: string; error?: string }> {
  const SAF = (FileSystem as any).StorageAccessFramework as {
    requestDirectoryPermissionsAsync: (initial?: string | null) => Promise<{ granted: boolean; directoryUri?: string }>;
    createFileAsync: (dirUri: string, name: string, mimeType: string) => Promise<string>;
    copyAsync: (options: { from: string; to: string }) => Promise<void>;
  } | undefined;

  if (Platform.OS !== 'android' || !SAF?.requestDirectoryPermissionsAsync) {
    // Non-Android or SAF unavailable -> fall back to the share sheet
    const ok = await shareExport(archivePath);
    return { saved: ok };
  }

  try {
    const perm = await SAF.requestDirectoryPermissionsAsync();
    if (!perm.granted || !perm.directoryUri) {
      return { saved: false, error: 'Folder selection cancelled.' };
    }

    const cleanName = suggestedName.replace(/\.[^.]+$/, '').slice(0, 60);
    const fileUri = await SAF.createFileAsync(perm.directoryUri, cleanName, 'application/zip');
    // Copy the archive file bytes into the created SAF file
    await SAF.copyAsync({ from: archivePath, to: fileUri });

    return { saved: true, directoryUri: perm.directoryUri };
  } catch (error) {
    const msg = error instanceof Error ? error.message : 'unknown error';
    warnDev('[Export] SAF save failed', { error: msg });
    return { saved: false, error: msg };
  }
}

export async function exportAndShare(): Promise<ExportResult> {
  const result = await generateExport();
  if (result.success && result.archivePath) {
    await shareExport(result.archivePath);
  }
  return result;
}

/**
 * Phase 4 — export a SINGLE plant (and its memories/photos) so users can back up
 * one plant off their phone. Reuses the same ZIP + share pattern.
 */
export async function generatePlantExport(plantId: string, options: ExportOptions = {}): Promise<ExportResult> {
  const { photoQuality = 'original', onProgress } = options;
  try {
    const existingDir = await FileSystem.getInfoAsync(exportTempDir);
    if (existingDir.exists) {
      await FileSystem.deleteAsync(exportTempDir, { idempotent: true });
    }
    await ensureDir(exportTempDir);

    // Load all saved plants, filter to the target
    const savedPlants = await readJsonSafe<{ savedPlants: unknown[] }>(
      `${gardenStorageDir}saved-plants.json`,
      { savedPlants: [] }
    );
    const plants = Array.isArray(savedPlants.savedPlants) ? savedPlants.savedPlants : [];
    const target = plants.find((p) => (p as Record<string, unknown>).id === plantId) as Record<string, unknown> | undefined;
    if (!target) {
      return { success: false, error: 'Plant not found.' };
    }

    const allEvents = Array.isArray(target.events) ? target.events as unknown[] : (Array.isArray(target.plantEvents) ? target.plantEvents as unknown[] : []);
    const intelligenceProfiles = await readJsonSafe<Record<string, unknown>>(
      `${gardenStorageDir}plant-intelligence-profiles.json`,
      {}
    );
    const intelligence = intelligenceProfiles[plantId] || {};
    const photoRecords = await loadPhotoRecords();

    // Photos: gather from plant fields + memory events
    const exportPhotosDir = `${exportTempDir}photos/`;
    await ensureDir(exportPhotosDir);
    let includedPhotos = 0;
    let missingPhotos = 0;

    const photoCandidates: string[] = [];
    const collect = (v: unknown) => {
      if (typeof v === 'string' && (v.startsWith('file://') || v.startsWith('ph://'))) photoCandidates.push(v);
      else if (Array.isArray(v)) v.forEach(collect);
      else if (v && typeof v === 'object') Object.values(v as Record<string, unknown>).forEach(collect);
    };
    collect(target);
    for (const ev of allEvents) collect((ev as Record<string, unknown>).photoUri);
    for (const ev of allEvents) collect((ev as Record<string, unknown>).uri);

    const seen = new Set<string>();
    const photoJobs: Array<{ source: string; dest: string }> = [];
    let i = 0;
    for (const uri of photoCandidates) {
      if (seen.has(uri)) continue;
      seen.add(uri);
      const filename = `photo_${i++}.jpg`;
      // If compressed quality requested, try to map this URI to a compressed copy
      let source = uri;
      if (photoQuality === 'compressed') {
        const rec = Object.values(photoRecords).find(
          (r) => (r as PhotoRecord).originalUri === uri || (r as PhotoRecord).activeUri === uri
        ) as PhotoRecord | undefined;
        if (rec?.compressedUri) source = rec.compressedUri;
      }
      photoJobs.push({ source, dest: `${exportPhotosDir}${filename}` });
    }

    const photoTotal = photoJobs.length;
    includedPhotos = await copyFilesConcurrent(photoJobs, (done) => {
      onProgress?.({ phase: 'photos', done, total: photoTotal });
    });
    missingPhotos = photoTotal - includedPhotos;

    await FileSystem.writeAsStringAsync(`${exportTempDir}plant.json`, JSON.stringify(target, null, 2));
    await FileSystem.writeAsStringAsync(`${exportTempDir}memories.json`, JSON.stringify(allEvents, null, 2));
    await FileSystem.writeAsStringAsync(`${exportTempDir}intelligence.json`, JSON.stringify(intelligence, null, 2));

    const manifest = {
      exportVersion: '1.1',
      exportedAt: new Date().toISOString(),
      appVersion: APP_VERSION,
      exportType: 'single_plant',
      plantCount: 1,
      memoryCount: allEvents.length,
      investigationCount: 0,
      intelligenceProfileCount: Object.keys(intelligence).length > 0 ? 1 : 0,
      photoCount: includedPhotos + missingPhotos,
      includedPhotos,
      missingPhotos,
    };
    await FileSystem.writeAsStringAsync(`${exportTempDir}manifest.json`, JSON.stringify(manifest, null, 2));

    const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
    const archiveName = `pixiesprout-plant-${plantId.slice(-8)}-${timestamp}.zip`;
    onProgress?.({ phase: 'archive', done: 0, total: 1 });
    const archivePath = await zip(exportTempDir, `${FileSystem.documentDirectory || FileSystem.cacheDirectory || ''}${archiveName}`);
    onProgress?.({ phase: 'archive', done: 1, total: 1 });

    return { success: true, archivePath, manifest };
  } catch (error) {
    const msg = error instanceof Error ? error.message : 'unknown error';
    return { success: false, error: msg };
  }
}

export async function exportAndSharePlant(plantId: string): Promise<ExportResult> {
  const result = await generatePlantExport(plantId);
  if (result.success && result.archivePath) {
    await shareExport(result.archivePath);
  }
  return result;
}
