import * as FileSystem from 'expo-file-system';

import type {
  AnalysisStatus,
  CandidateLearning,
  InterventionRecord,
  InvestigationContextPacket,
  PhotoIntelligenceResult,
  PhotoIntelligenceType,
  PlantIntelligenceProfile,
} from './types';
import { readJsonFile, withFileLock, writeJsonFile } from './jsonFileStorage';
import { logDev } from './runtimeUtils';

type IntelligenceSummaryField =
  | 'identitySummary'
  | 'environmentSummary'
  | 'placementSummary'
  | 'setupSummary'
  | 'historySummary'
  | 'activeIssuesSummary'
  | 'growthPatternSummary'
  | 'recentTrendSummary';

type UnknownRecord = Record<string, unknown>;

const storageLockKey = 'intelligence-profiles';
const gardenStorageDir = `${FileSystem.documentDirectory || ''}pixiesprout-garden/`;
const intelligenceProfilesPath = `${gardenStorageDir}plant-intelligence-profiles.json`;
const epochIso = new Date(0).toISOString();
const validAnalysisStatuses = new Set<AnalysisStatus>(['pending', 'analyzing', 'complete', 'failed']);
const validPhotoTypes = new Set<PhotoIntelligenceType>(['environment', 'placement', 'setup', 'progress']);
const statusPriority: Record<AnalysisStatus, number> = {
  pending: 1,
  analyzing: 2,
  failed: 3,
  complete: 4,
};

function asRecord(value: unknown): UnknownRecord | null {
  return value !== null && typeof value === 'object' && !Array.isArray(value)
    ? value as UnknownRecord
    : null;
}

function stringValue(value: unknown, fallback = ''): string {
  return typeof value === 'string' ? value : fallback;
}

function nonEmptyString(value: unknown): string | null {
  return typeof value === 'string' && value.trim() ? value.trim() : null;
}

function recordArray<T>(value: unknown): T[] {
  return Array.isArray(value)
    ? value.filter(item => asRecord(item) !== null) as T[]
    : [];
}

function timestamp(value: unknown): number {
  if (typeof value !== 'string' && typeof value !== 'number' && !(value instanceof Date)) return 0;
  const parsed = value instanceof Date ? value.getTime() : new Date(value).getTime();
  return Number.isFinite(parsed) ? parsed : 0;
}

function normalizePhotoResult(value: unknown): PhotoIntelligenceResult | null {
  const source = asRecord(value);
  if (!source) return null;

  const photoUri = nonEmptyString(source.photoUri);
  const photoType = source.photoType;
  if (!photoUri || typeof photoType !== 'string' || !validPhotoTypes.has(photoType as PhotoIntelligenceType)) {
    return null;
  }

  const analysisStatus = typeof source.analysisStatus === 'string'
    && validAnalysisStatuses.has(source.analysisStatus as AnalysisStatus)
    ? source.analysisStatus as AnalysisStatus
    : 'failed';

  const normalized: PhotoIntelligenceResult = {
    photoUri,
    photoType: photoType as PhotoIntelligenceType,
    summary: stringValue(source.summary),
    extractedAt: nonEmptyString(source.extractedAt) || epochIso,
    analysisStatus,
    retryCount: Number.isFinite(source.retryCount)
      ? Math.max(0, Math.floor(source.retryCount as number))
      : 0,
  };

  const lastAnalysisAttemptAt = nonEmptyString(source.lastAnalysisAttemptAt);
  const analysisErrorMessage = nonEmptyString(source.analysisErrorMessage);
  if (lastAnalysisAttemptAt) normalized.lastAnalysisAttemptAt = lastAnalysisAttemptAt;
  if (analysisErrorMessage && analysisStatus === 'failed') {
    normalized.analysisErrorMessage = analysisErrorMessage;
  }

  return normalized;
}

function photoResultKey(result: Pick<PhotoIntelligenceResult, 'photoUri' | 'photoType'>): string {
  return `${result.photoType}\u0000${result.photoUri}`;
}

function photoAttemptTimestamp(result: PhotoIntelligenceResult): number {
  return timestamp(result.lastAnalysisAttemptAt) || timestamp(result.extractedAt);
}

function chooseNewerPhotoResult(
  current: PhotoIntelligenceResult,
  candidate: PhotoIntelligenceResult,
): PhotoIntelligenceResult {
  const currentTimestamp = photoAttemptTimestamp(current);
  const candidateTimestamp = photoAttemptTimestamp(candidate);
  if (candidateTimestamp < currentTimestamp) return current;
  if (candidateTimestamp > currentTimestamp) return candidate;
  return statusPriority[candidate.analysisStatus] >= statusPriority[current.analysisStatus]
    ? candidate
    : current;
}

function dedupePhotoHistory(value: unknown): PhotoIntelligenceResult[] {
  if (!Array.isArray(value)) return [];

  const results: PhotoIntelligenceResult[] = [];
  const indexByKey = new Map<string, number>();

  for (const item of value) {
    const normalized = normalizePhotoResult(item);
    if (!normalized) continue;
    const key = photoResultKey(normalized);
    const existingIndex = indexByKey.get(key);
    if (existingIndex === undefined) {
      indexByKey.set(key, results.length);
      results.push(normalized);
      continue;
    }
    results[existingIndex] = mergePhotoResult(results[existingIndex], normalized);
  }

  return results;
}

function normalizeProfile(value: unknown, fallbackPlantId: string): PlantIntelligenceProfile {
  const source = asRecord(value) || {};
  const now = new Date().toISOString();
  const plantId = nonEmptyString(fallbackPlantId) || nonEmptyString(source.plantId) || '';

  return {
    ...(source as Partial<PlantIntelligenceProfile>),
    plantId,
    identitySummary: stringValue(source.identitySummary),
    environmentSummary: stringValue(source.environmentSummary),
    placementSummary: stringValue(source.placementSummary),
    setupSummary: stringValue(source.setupSummary),
    historySummary: stringValue(source.historySummary),
    activeIssuesSummary: stringValue(source.activeIssuesSummary),
    successfulInterventions: recordArray<InterventionRecord>(source.successfulInterventions),
    unsuccessfulInterventions: recordArray<InterventionRecord>(source.unsuccessfulInterventions),
    growthPatternSummary: stringValue(source.growthPatternSummary),
    recentTrendSummary: stringValue(source.recentTrendSummary),
    candidateLearnings: recordArray<CandidateLearning>(source.candidateLearnings),
    photoIntelligenceHistory: dedupePhotoHistory(source.photoIntelligenceHistory),
    createdAt: nonEmptyString(source.createdAt) || now,
    updatedAt: nonEmptyString(source.updatedAt) || now,
  };
}

async function loadIntelligenceProfilesUnlocked(): Promise<Record<string, PlantIntelligenceProfile>> {
  const raw = await readJsonFile<unknown>(intelligenceProfilesPath, {});
  const source = asRecord(raw);
  if (!source) return {};

  const profiles: Record<string, PlantIntelligenceProfile> = {};
  for (const [plantId, value] of Object.entries(source)) {
    const normalizedId = nonEmptyString(plantId);
    if (!normalizedId) continue;
    profiles[normalizedId] = normalizeProfile(value, normalizedId);
  }
  return profiles;
}

async function saveIntelligenceProfilesUnlocked(
  profiles: Record<string, PlantIntelligenceProfile>,
): Promise<void> {
  await writeJsonFile(gardenStorageDir, intelligenceProfilesPath, profiles);
}

async function mutateProfile(
  plantId: string,
  mutate: (profile: PlantIntelligenceProfile) => PlantIntelligenceProfile,
): Promise<PlantIntelligenceProfile> {
  const normalizedId = nonEmptyString(plantId);
  if (!normalizedId) throw new Error('plantId is required');

  return withFileLock(storageLockKey, async () => {
    const profiles = await loadIntelligenceProfilesUnlocked();
    const current = profiles[normalizedId] || createEmptyIntelligenceProfile(normalizedId);
    const candidate = mutate(current);
    if (candidate === current) return current;
    const updated = normalizeProfile(candidate, normalizedId);
    profiles[normalizedId] = updated;
    await saveIntelligenceProfilesUnlocked(profiles);
    return updated;
  });
}

let lastIdTimestamp = 0;
let sameMillisecondSequence = 0;

export function createIntelligenceId(prefix: string): string {
  const safePrefix = prefix.trim().replace(/[^a-zA-Z0-9_-]+/g, '_') || 'intelligence';
  const now = Date.now();
  sameMillisecondSequence = now === lastIdTimestamp ? sameMillisecondSequence + 1 : 0;
  lastIdTimestamp = now;
  const entropy = Math.floor(Math.random() * 0x7fffffff) + sameMillisecondSequence;
  return `${safePrefix}_${now}_${entropy.toString(36)}`;
}

export function createEmptyIntelligenceProfile(plantId: string): PlantIntelligenceProfile {
  const now = new Date().toISOString();
  return {
    plantId,
    identitySummary: '',
    environmentSummary: '',
    placementSummary: '',
    setupSummary: '',
    historySummary: '',
    activeIssuesSummary: '',
    successfulInterventions: [],
    unsuccessfulInterventions: [],
    growthPatternSummary: '',
    recentTrendSummary: '',
    candidateLearnings: [],
    photoIntelligenceHistory: [],
    createdAt: now,
    updatedAt: now,
  };
}

export async function loadIntelligenceProfiles(): Promise<Record<string, PlantIntelligenceProfile>> {
  return withFileLock(storageLockKey, loadIntelligenceProfilesUnlocked);
}

export async function loadIntelligenceProfile(plantId: string): Promise<PlantIntelligenceProfile | null> {
  const normalizedId = nonEmptyString(plantId);
  if (!normalizedId) return null;
  const profiles = await loadIntelligenceProfiles();
  return profiles[normalizedId] || null;
}

export async function deleteIntelligenceProfile(plantId: string): Promise<boolean> {
  const normalizedId = nonEmptyString(plantId);
  if (!normalizedId) return false;

  return withFileLock(storageLockKey, async () => {
    const profiles = await loadIntelligenceProfilesUnlocked();
    if (!(normalizedId in profiles)) return false;
    delete profiles[normalizedId];
    await saveIntelligenceProfilesUnlocked(profiles);
    return true;
  });
}

export async function saveIntelligenceProfile(profile: PlantIntelligenceProfile): Promise<void> {
  const normalizedId = nonEmptyString(profile?.plantId);
  if (!normalizedId) throw new Error('profile.plantId is required');

  await withFileLock(storageLockKey, async () => {
    const profiles = await loadIntelligenceProfilesUnlocked();
    profiles[normalizedId] = normalizeProfile(profile, normalizedId);
    await saveIntelligenceProfilesUnlocked(profiles);
  });
  logDev('[PlantIntelligence] profile saved', { plantId: normalizedId });
}

export async function getOrCreateIntelligenceProfile(plantId: string): Promise<PlantIntelligenceProfile> {
  const normalizedId = nonEmptyString(plantId);
  if (!normalizedId) throw new Error('plantId is required');
  const existingProfile = await loadIntelligenceProfile(normalizedId);
  return existingProfile || createEmptyIntelligenceProfile(normalizedId);
}

export async function updateIntelligenceSummary(
  plantId: string,
  field: IntelligenceSummaryField,
  value: string,
): Promise<PlantIntelligenceProfile> {
  return mutateProfile(plantId, profile => ({
    ...profile,
    [field]: typeof value === 'string' ? value : '',
    updatedAt: new Date().toISOString(),
  }));
}

function replaceRecordById<T extends { id: string }>(records: T[], record: T): T[] {
  const recordId = nonEmptyString(record?.id);
  if (!recordId) return [...records, record];

  const firstIndex = records.findIndex(existing => existing?.id === recordId);
  if (firstIndex < 0) return [...records, record];

  const withoutDuplicates = records.filter(existing => existing?.id !== recordId);
  withoutDuplicates.splice(Math.min(firstIndex, withoutDuplicates.length), 0, record);
  return withoutDuplicates;
}

export async function addIntervention(
  plantId: string,
  intervention: InterventionRecord,
): Promise<PlantIntelligenceProfile> {
  if (intervention?.outcome !== 'successful' && intervention?.outcome !== 'unsuccessful') {
    throw new Error('intervention.outcome must be successful or unsuccessful');
  }

  return mutateProfile(plantId, profile => {
    const interventionId = nonEmptyString(intervention?.id);
    const withoutMatchingSuccessful = interventionId
      ? profile.successfulInterventions.filter(item => item?.id !== interventionId)
      : profile.successfulInterventions;
    const withoutMatchingUnsuccessful = interventionId
      ? profile.unsuccessfulInterventions.filter(item => item?.id !== interventionId)
      : profile.unsuccessfulInterventions;

    return {
      ...profile,
      successfulInterventions: intervention.outcome === 'successful'
        ? replaceRecordById(withoutMatchingSuccessful, intervention)
        : withoutMatchingSuccessful,
      unsuccessfulInterventions: intervention.outcome === 'unsuccessful'
        ? replaceRecordById(withoutMatchingUnsuccessful, intervention)
        : withoutMatchingUnsuccessful,
      updatedAt: new Date().toISOString(),
    };
  });
}

export async function addCandidateLearning(
  plantId: string,
  learning: CandidateLearning,
): Promise<PlantIntelligenceProfile> {
  return mutateProfile(plantId, profile => ({
    ...profile,
    candidateLearnings: replaceRecordById(profile.candidateLearnings, learning),
    updatedAt: new Date().toISOString(),
  }));
}

function mergePhotoResult(
  existing: PhotoIntelligenceResult | undefined,
  incoming: PhotoIntelligenceResult,
): PhotoIntelligenceResult {
  if (!existing) return incoming;

  const accepted = chooseNewerPhotoResult(existing, incoming);
  if (accepted === existing) return existing;

  const summary = incoming.analysisStatus === 'complete' && incoming.summary.trim()
    ? incoming.summary
    : existing.summary || incoming.summary;

  return {
    ...existing,
    ...incoming,
    summary,
    analysisErrorMessage: incoming.analysisStatus === 'failed'
      ? incoming.analysisErrorMessage
      : undefined,
  };
}

export async function addPhotoIntelligence(
  plantId: string,
  result: PhotoIntelligenceResult,
): Promise<PlantIntelligenceProfile> {
  const normalizedResult = normalizePhotoResult(result);
  if (!normalizedResult) throw new Error('A valid photoUri and photoType are required');

  return mutateProfile(plantId, profile => {
    const key = photoResultKey(normalizedResult);
    const existingIndex = profile.photoIntelligenceHistory.findIndex(
      entry => photoResultKey(entry) === key,
    );
    const existing = existingIndex >= 0
      ? profile.photoIntelligenceHistory[existingIndex]
      : undefined;
    const merged = mergePhotoResult(existing, normalizedResult);
    if (existing && merged === existing) return profile;

    const photoIntelligenceHistory = [...profile.photoIntelligenceHistory];
    if (existingIndex >= 0) {
      photoIntelligenceHistory[existingIndex] = merged;
    } else {
      photoIntelligenceHistory.push(merged);
    }

    const summaryPatch: Partial<Pick<PlantIntelligenceProfile, IntelligenceSummaryField>> = {};
    const completedSummary = merged.analysisStatus === 'complete' ? merged.summary.trim() : '';
    if (completedSummary) {
      if (merged.photoType === 'environment') summaryPatch.environmentSummary = completedSummary;
      if (merged.photoType === 'placement') summaryPatch.placementSummary = completedSummary;
      if (merged.photoType === 'setup') summaryPatch.setupSummary = completedSummary;
      if (merged.photoType === 'progress') summaryPatch.growthPatternSummary = completedSummary;
    }

    return {
      ...profile,
      ...summaryPatch,
      photoIntelligenceHistory,
      updatedAt: new Date().toISOString(),
    };
  });
}

export async function buildInvestigationContextPacket(
  plantId: string,
): Promise<InvestigationContextPacket | null> {
  const profile = await loadIntelligenceProfile(plantId);
  if (!profile) return null;

  return {
    identitySummary: profile.identitySummary,
    environmentSummary: profile.environmentSummary,
    placementSummary: profile.placementSummary,
    setupSummary: profile.setupSummary,
    historySummary: profile.historySummary,
    activeIssuesSummary: profile.activeIssuesSummary,
    successfulInterventions: profile.successfulInterventions,
    unsuccessfulInterventions: profile.unsuccessfulInterventions,
    growthPatternSummary: profile.growthPatternSummary,
    recentTrendSummary: profile.recentTrendSummary,
    candidateLearnings: profile.candidateLearnings,
  };
}
