declare const process: {
  env: {
    EXPO_PUBLIC_PROXY_URL?: string;
    EXPO_PUBLIC_APP_TOKEN?: string;
  };
};

import {
  addPhotoIntelligence,
  loadIntelligenceProfile,
  loadIntelligenceProfiles,
} from './plantIntelligenceService';
import { buildPlantMemoryContext, renderMemoryContextBlock } from './memoryContext';
import {
  buildPhotoAnalysisPrompt,
  fetchWithTimeout,
  optimizePhotoForAnalysis,
  parsePhotoSummary,
} from './photoAnalysisUtils';
import { logDev, warnDev } from './runtimeUtils';
import type {
  PhotoAnalysisInput,
  PhotoIntelligenceResult,
  PlantIntelligenceProfile,
  RecoveredPhotoAnalysis,
} from './types';
import { runAiCall } from '../orchestration/aiQueue';

const proxyEndpoint = process.env.EXPO_PUBLIC_PROXY_URL
  || 'https://pixiesprout-ai.46-4-121-190.sslip.io/v1/chat/completions';
const xiaomiModel = 'qwen3.5:397b';
const requestTimeoutMs = 30_000;
const scanPriorityTimeoutMs = 120_000;
const stuckAnalysisThresholdMs = 10 * 60 * 1000;
const fallbackSummary = 'Photo analysis was not available at this time.';

// A depth counter is used instead of a boolean so overlapping scans cannot
// release one another's priority lock prematurely.
let scanPriorityDepth = 0;
const scanWaiters = new Set<() => void>();
const inFlightAnalyses = new Map<string, Promise<PhotoIntelligenceResult>>();

/** Call when a plant scan starts — pauses background photo analysis. */
export function acquireScanPriority(): void {
  scanPriorityDepth += 1;
  logDev('[PhotoIntelligence] scan priority acquired', { depth: scanPriorityDepth });
}

/** Call when a plant scan ends — resumes analysis after the final active scan. */
export function releaseScanPriority(): void {
  if (scanPriorityDepth === 0) {
    warnDev('[PhotoIntelligence] scan priority released without a matching acquire', {});
    return;
  }

  scanPriorityDepth -= 1;
  if (scanPriorityDepth > 0) {
    logDev('[PhotoIntelligence] scan priority release deferred', { depth: scanPriorityDepth });
    return;
  }

  for (const resolve of [...scanWaiters]) resolve();
  logDev('[PhotoIntelligence] scan priority released; resuming photo analysis', {});
}

/** Returns true if one or more scans are currently blocking. */
export function isScanPriorityActive(): boolean {
  return scanPriorityDepth > 0;
}

/** Wait for active scans, but do not block forever if a release is missed. */
function waitForScanPriority(): Promise<void> {
  if (!isScanPriorityActive()) return Promise.resolve();

  return new Promise(resolve => {
    let finished = false;
    let timer: ReturnType<typeof setTimeout>;
    const finish = () => {
      if (finished) return;
      finished = true;
      clearTimeout(timer);
      scanWaiters.delete(finish);
      resolve();
    };
    timer = setTimeout(finish, scanPriorityTimeoutMs);
    scanWaiters.add(finish);
  });
}

function getProxyToken(): string {
  return (process.env.EXPO_PUBLIC_APP_TOKEN || '').trim();
}

function isoNow(): string {
  return new Date().toISOString();
}

function resultKey(input: Pick<PhotoAnalysisInput, 'plantId' | 'photoUri' | 'photoType'>): string {
  return JSON.stringify([input.plantId, input.photoType, input.photoUri]);
}

function resultTimestamp(result: PhotoIntelligenceResult | undefined): number {
  if (!result) return 0;
  const timestamp = new Date(result.lastAnalysisAttemptAt || result.extractedAt).getTime();
  return Number.isFinite(timestamp) ? timestamp : 0;
}

function nextAttemptIso(existing: PhotoIntelligenceResult | undefined): string {
  return new Date(Math.max(Date.now(), resultTimestamp(existing) + 1)).toISOString();
}

function buildFallbackResult(
  input: PhotoAnalysisInput,
  errorMessage?: string,
  retryCount = 0,
): PhotoIntelligenceResult {
  const now = isoNow();
  return {
    photoUri: input.photoUri,
    photoType: input.photoType,
    summary: fallbackSummary,
    extractedAt: now,
    analysisStatus: 'failed',
    lastAnalysisAttemptAt: now,
    analysisErrorMessage: errorMessage,
    retryCount,
  };
}

function buildPendingResult(
  input: PhotoAnalysisInput,
  existing: PhotoIntelligenceResult | undefined,
  retryCount: number,
  attemptAt: string,
): PhotoIntelligenceResult {
  return {
    photoUri: input.photoUri,
    photoType: input.photoType,
    summary: existing?.summary || '',
    extractedAt: existing?.extractedAt || attemptAt,
    analysisStatus: 'pending',
    lastAnalysisAttemptAt: attemptAt,
    retryCount,
  };
}

function findPhotoResult(
  profile: PlantIntelligenceProfile | null | undefined,
  input: Pick<PhotoAnalysisInput, 'photoUri' | 'photoType'>,
): PhotoIntelligenceResult | undefined {
  const history = Array.isArray(profile?.photoIntelligenceHistory)
    ? profile.photoIntelligenceHistory
    : [];
  for (let index = history.length - 1; index >= 0; index -= 1) {
    const result = history[index];
    if (result.photoUri === input.photoUri && result.photoType === input.photoType) {
      return result;
    }
  }
  return undefined;
}

function isFreshInProgress(result: PhotoIntelligenceResult): boolean {
  if (result.analysisStatus !== 'pending' && result.analysisStatus !== 'analyzing') return false;
  const attemptAt = resultTimestamp(result);
  return attemptAt > 0 && Date.now() - attemptAt < stuckAnalysisThresholdMs;
}

export async function recoverStuckAnalyses(): Promise<RecoveredPhotoAnalysis[]> {
  const profiles = await loadIntelligenceProfiles();
  const recovered: RecoveredPhotoAnalysis[] = [];
  const cutoff = Date.now() - stuckAnalysisThresholdMs;

  for (const [plantId, profile] of Object.entries(profiles)) {
    for (const entry of profile.photoIntelligenceHistory) {
      if (entry.analysisStatus !== 'pending' && entry.analysisStatus !== 'analyzing') continue;
      const attemptTime = resultTimestamp(entry);
      if (attemptTime > cutoff) continue;

      const failedResult: PhotoIntelligenceResult = {
        ...entry,
        analysisStatus: 'failed',
        analysisErrorMessage: 'App was closed during analysis',
        lastAnalysisAttemptAt: entry.lastAnalysisAttemptAt || entry.extractedAt,
      };

      try {
        const updatedProfile = await addPhotoIntelligence(plantId, failedResult);
        const stored = findPhotoResult(updatedProfile, entry);
        if (
          stored?.analysisStatus === 'failed'
          && stored.lastAnalysisAttemptAt === failedResult.lastAnalysisAttemptAt
        ) {
          recovered.push({ plantId, photoUri: entry.photoUri, photoType: entry.photoType });
        }
      } catch (error) {
        warnDev('[PhotoIntelligence] failed to recover a stuck analysis', {
          plantId,
          photoUri: entry.photoUri,
          error: error instanceof Error ? error.message : 'storage error',
        });
      }
    }
  }

  if (recovered.length) {
    logDev('[PhotoIntelligence] recovered stuck analyses', { count: recovered.length });
  }
  return recovered;
}

export async function analyzePhoto(input: PhotoAnalysisInput): Promise<PhotoIntelligenceResult> {
  if (!input.photoUri?.trim()) {
    return buildFallbackResult(input, 'Photo URI missing');
  }

  try {
    const apiKey = getProxyToken();
    if (!apiKey) {
      warnDev('[PhotoIntelligence] API key missing', {});
      return buildFallbackResult(input, 'API key missing');
    }

    const imageBase64 = await optimizePhotoForAnalysis(input.photoUri);
    if (!imageBase64) {
      warnDev('[PhotoIntelligence] optimized photo was empty', { photoUri: input.photoUri });
      return buildFallbackResult(input, 'Photo could not be read');
    }

    let promptInput = input;
    if (!input.plantMemory) {
      try {
        const memory = await buildPlantMemoryContext(input.plantId);
        const block = renderMemoryContextBlock(memory);
        if (block) promptInput = { ...input, plantMemory: block };
      } catch {
        // Memory is supplementary; continue without it.
      }
    }
    const prompt = buildPhotoAnalysisPrompt(promptInput);

    const requestBody = {
      model: xiaomiModel,
      messages: [{
        role: 'user',
        content: [
          { type: 'text', text: prompt },
          { type: 'image_url', image_url: { url: `data:image/jpeg;base64,${imageBase64}` } },
        ],
      }],
      temperature: 0.2,
      max_tokens: 700,
      response_format: { type: 'json_object' },
    };

    logDev('[PhotoIntelligence] request started', {
      endpoint: proxyEndpoint,
      model: xiaomiModel,
      photoType: input.photoType,
      hasPlantCommonName: Boolean(input.plantCommonName),
      hasPlantScientificName: Boolean(input.plantScientificName),
      imageBase64Length: imageBase64.length,
    });

    const response = await runAiCall(
      () => fetchWithTimeout(proxyEndpoint, {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${apiKey}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(requestBody),
      }, requestTimeoutMs),
      'high',
    );

    if (!response.ok) {
      warnDev('[PhotoIntelligence] API error', { status: response.status });
      return buildFallbackResult(input, `API returned status ${response.status}`);
    }

    const data = await response.json() as {
      choices?: Array<{
        message?: { content?: unknown };
        finish_reason?: string;
      }>;
    };
    const choice = data.choices?.[0];
    if (choice?.finish_reason === 'length') {
      warnDev('[PhotoIntelligence] response truncated', {});
      return buildFallbackResult(input, 'Analysis was truncated');
    }

    const content = typeof choice?.message?.content === 'string'
      ? choice.message.content
      : '';
    if (!content) {
      warnDev('[PhotoIntelligence] empty response content', {});
      return buildFallbackResult(input, 'Empty response from analysis');
    }

    const summary = parsePhotoSummary(content);
    if (!summary) {
      warnDev('[PhotoIntelligence] response did not contain a valid summary', {
        contentLength: content.length,
      });
      return buildFallbackResult(input, 'Analysis response did not contain a summary');
    }

    const now = isoNow();
    logDev('[PhotoIntelligence] response received', {
      contentLength: content.length,
      summaryLength: summary.length,
    });
    return {
      photoUri: input.photoUri,
      photoType: input.photoType,
      summary,
      extractedAt: now,
      analysisStatus: 'complete',
      lastAnalysisAttemptAt: now,
      retryCount: 0,
    };
  } catch (error) {
    const message = error instanceof Error ? error.message : 'unknown error';
    const isTimeout = error instanceof Error && error.name === 'AbortError';
    const errorText = isTimeout ? 'Request timed out after 30s' : message;
    warnDev('[PhotoIntelligence] analysis failed', {
      photoType: input.photoType,
      error: errorText,
      isTimeout,
    });
    return buildFallbackResult(input, errorText);
  }
}

async function analyzeAndStorePhotoInternal(
  input: PhotoAnalysisInput,
): Promise<PhotoIntelligenceResult> {
  let existing: PhotoIntelligenceResult | undefined;
  let retryCount = 0;
  let attemptAt = isoNow();

  try {
    const profile = await loadIntelligenceProfile(input.plantId);
    existing = findPhotoResult(profile, input);

    if (existing && !input.forceReanalyze) {
      if (existing.analysisStatus === 'complete') {
        logDev('[PhotoIntelligence] skipping already-complete photo', {
          photoUri: input.photoUri,
          photoType: input.photoType,
        });
        return existing;
      }
      if (isFreshInProgress(existing)) {
        logDev('[PhotoIntelligence] skipping in-progress photo', {
          photoUri: input.photoUri,
          photoType: input.photoType,
        });
        return existing;
      }
    }

    retryCount = existing?.analysisStatus === 'failed'
      || (existing ? !isFreshInProgress(existing) && existing.analysisStatus !== 'complete' : false)
      ? (existing?.retryCount || 0) + 1
      : existing?.retryCount || 0;
    attemptAt = nextAttemptIso(existing);

    const pendingResult = buildPendingResult(input, existing, retryCount, attemptAt);
    await addPhotoIntelligence(input.plantId, pendingResult);

    await waitForScanPriority();

    const analyzingResult: PhotoIntelligenceResult = {
      ...pendingResult,
      analysisStatus: 'analyzing',
      analysisErrorMessage: undefined,
    };
    await addPhotoIntelligence(input.plantId, analyzingResult);

    const analyzed = await analyzePhoto(input);
    const finalResult: PhotoIntelligenceResult = {
      ...analyzed,
      lastAnalysisAttemptAt: attemptAt,
      retryCount,
    };

    try {
      await addPhotoIntelligence(input.plantId, finalResult);
    } catch (storageError) {
      warnDev('[PhotoIntelligence] analysis completed but final persistence failed', {
        plantId: input.plantId,
        photoType: input.photoType,
        error: storageError instanceof Error ? storageError.message : 'storage error',
      });
    }
    return finalResult;
  } catch (error) {
    const message = error instanceof Error ? error.message : 'storage error';
    warnDev('[PhotoIntelligence] analysis workflow failed', {
      plantId: input.plantId,
      photoType: input.photoType,
      error: message,
    });
    const fallback = buildFallbackResult(input, message, retryCount);
    fallback.lastAnalysisAttemptAt = attemptAt;
    try {
      await addPhotoIntelligence(input.plantId, fallback);
    } catch {
      // Nothing else can be persisted; return the accurate failure to the caller.
    }
    return fallback;
  }
}

export function analyzeAndStorePhoto(input: PhotoAnalysisInput): Promise<PhotoIntelligenceResult> {
  const key = resultKey(input);
  const existingTask = inFlightAnalyses.get(key);
  if (existingTask) return existingTask;

  const task = analyzeAndStorePhotoInternal(input).finally(() => {
    if (inFlightAnalyses.get(key) === task) {
      inFlightAnalyses.delete(key);
    }
  });
  inFlightAnalyses.set(key, task);
  return task;
}

export function retryPhotoAnalysis(input: PhotoAnalysisInput): Promise<PhotoIntelligenceResult> {
  return analyzeAndStorePhoto({ ...input, forceReanalyze: true });
}
