/**
 * @ai-summary Hook to orchestrate receipt OCR extraction for fuel logs
 * @ai-context Handles camera capture -> OCR extraction -> field mapping flow
 */

import { useState, useCallback } from 'react';
import { apiClient } from '../../../core/api/client';
import { FuelGrade } from '../types/fuel-logs.types';

/** OCR-extracted receipt field with confidence */
export interface ExtractedReceiptField {
  value: string | number | null;
  confidence: number;
}

/** Fields extracted from a fuel receipt */
export interface ExtractedReceiptFields {
  transactionDate: ExtractedReceiptField;
  totalAmount: ExtractedReceiptField;
  fuelQuantity: ExtractedReceiptField;
  pricePerUnit: ExtractedReceiptField;
  fuelGrade: ExtractedReceiptField;
  merchantName: ExtractedReceiptField;
}

/** Mapped fields ready for form population */
export interface MappedFuelLogFields {
  dateTime?: string;
  fuelUnits?: number;
  costPerUnit?: number;
  fuelGrade?: FuelGrade;
  locationData?: {
    stationName?: string;
    googlePlaceId?: string;
    address?: string;
  };
}

/** Matched station from receipt merchant name */
export interface MatchedStation {
  placeId: string;
  name: string;
  address: string;
}

/** Receipt OCR result */
export interface ReceiptOcrResult {
  extractedFields: ExtractedReceiptFields;
  mappedFields: MappedFuelLogFields;
  rawText: string;
  overallConfidence: number;
  matchedStation: MatchedStation | null;
}

/** Hook state */
export interface UseReceiptOcrState {
  isCapturing: boolean;
  isProcessing: boolean;
  result: ReceiptOcrResult | null;
  receiptImageUrl: string | null;
  error: string | null;
}

/** Hook return type */
export interface UseReceiptOcrReturn extends UseReceiptOcrState {
  startCapture: () => void;
  cancelCapture: () => void;
  processImage: (file: File, croppedFile?: File) => Promise<void>;
  acceptResult: () => MappedFuelLogFields | null;
  reset: () => void;
  updateField: (fieldName: keyof ExtractedReceiptFields, value: string | number | null) => void;
  clearMatchedStation: () => void;
}

/** Confidence threshold for highlighting low-confidence fields */
export const LOW_CONFIDENCE_THRESHOLD = 0.7;

/** Map fuel grade string to valid FuelGrade enum */
function mapFuelGrade(value: string | number | null): FuelGrade {
  if (!value) return null;
  const stringValue = String(value).toLowerCase().trim();

  // Map common grade values
  const gradeMap: Record<string, FuelGrade> = {
    '87': '87',
    'regular': '87',
    'unleaded': '87',
    '88': '88',
    '89': '89',
    'mid': '89',
    'midgrade': '89',
    'plus': '89',
    '91': '91',
    'premium': '91',
    '93': '93',
    'super': '93',
    '#1': '#1',
    '#2': '#2',
    'diesel': '#2',
  };

  return gradeMap[stringValue] || null;
}

/** Parse date string to ISO format */
function parseTransactionDate(value: string | number | null): string | undefined {
  if (!value) return undefined;

  const dateStr = String(value);

  // Try to parse various date formats
  const date = new Date(dateStr);
  if (!isNaN(date.getTime())) {
    return date.toISOString();
  }

  // Try MM/DD/YYYY format
  const mdyMatch = dateStr.match(/(\d{1,2})\/(\d{1,2})\/(\d{4})/);
  if (mdyMatch) {
    const [, month, day, year] = mdyMatch;
    const parsed = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
    if (!isNaN(parsed.getTime())) {
      return parsed.toISOString();
    }
  }

  return undefined;
}

/** Parse numeric value */
function parseNumber(value: string | number | null): number | undefined {
  if (value === null || value === undefined) return undefined;
  if (typeof value === 'number') return value;

  // Remove currency symbols, commas, and extra whitespace
  const cleaned = value.replace(/[$,\s]/g, '');
  const num = parseFloat(cleaned);

  return isNaN(num) ? undefined : num;
}

/** Extract receipt data from image using OCR service */
async function extractReceiptFromImage(file: File): Promise<{
  extractedFields: ExtractedReceiptFields;
  rawText: string;
  confidence: number;
}> {
  const formData = new FormData();
  formData.append('file', file);

  const response = await apiClient.post('/ocr/extract/receipt', formData, {
    headers: { 'Content-Type': 'multipart/form-data' },
    timeout: 30000, // 30 seconds for OCR processing
  });

  const data = response.data;

  if (!data.success) {
    throw new Error('OCR extraction failed');
  }

  // Map OCR response to ExtractedReceiptFields
  const fields = data.extractedFields || {};

  const extractedFields: ExtractedReceiptFields = {
    transactionDate: {
      value: fields.transactionDate?.value || null,
      confidence: fields.transactionDate?.confidence || 0,
    },
    totalAmount: {
      value: fields.totalAmount?.value || null,
      confidence: fields.totalAmount?.confidence || 0,
    },
    fuelQuantity: {
      value: fields.fuelQuantity?.value || null,
      confidence: fields.fuelQuantity?.confidence || 0,
    },
    pricePerUnit: {
      value: fields.pricePerUnit?.value || null,
      confidence: fields.pricePerUnit?.confidence || 0,
    },
    fuelGrade: {
      value: fields.fuelGrade?.value || null,
      confidence: fields.fuelGrade?.confidence || 0,
    },
    merchantName: {
      value: fields.merchantName?.value || null,
      confidence: fields.merchantName?.confidence || 0,
    },
  };

  return {
    extractedFields,
    rawText: data.rawText || '',
    confidence: data.confidence || 0,
  };
}

/** Match station from merchant name via backend */
async function matchStationFromMerchant(merchantName: string): Promise<MatchedStation | null> {
  try {
    const response = await apiClient.post('/stations/match', { merchantName });
    const data = response.data;

    if (data.matched && data.station) {
      return {
        placeId: data.station.placeId,
        name: data.station.name,
        address: data.station.address,
      };
    }
    return null;
  } catch (err) {
    console.error('Station matching failed (non-blocking):', err);
    return null;
  }
}

/** Map extracted fields to fuel log form fields */
function mapFieldsToFuelLog(
  fields: ExtractedReceiptFields,
  matchedStation?: MatchedStation | null
): MappedFuelLogFields {
  // If station was matched, use matched data; otherwise fall back to merchant name
  const locationData = matchedStation
    ? {
        stationName: matchedStation.name,
        googlePlaceId: matchedStation.placeId,
        address: matchedStation.address,
      }
    : fields.merchantName.value
      ? { stationName: String(fields.merchantName.value) }
      : undefined;

  return {
    dateTime: parseTransactionDate(fields.transactionDate.value),
    fuelUnits: parseNumber(fields.fuelQuantity.value),
    costPerUnit: parseNumber(fields.pricePerUnit.value),
    fuelGrade: mapFuelGrade(fields.fuelGrade.value),
    locationData,
  };
}

/**
 * Hook to orchestrate receipt photo capture and OCR extraction
 */
export function useReceiptOcr(): UseReceiptOcrReturn {
  const [isCapturing, setIsCapturing] = useState(false);
  const [isProcessing, setIsProcessing] = useState(false);
  const [result, setResult] = useState<ReceiptOcrResult | null>(null);
  const [receiptImageUrl, setReceiptImageUrl] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  const startCapture = useCallback(() => {
    setIsCapturing(true);
    setError(null);
    setResult(null);
  }, []);

  const cancelCapture = useCallback(() => {
    setIsCapturing(false);
    setError(null);
  }, []);

  const processImage = useCallback(async (file: File, croppedFile?: File) => {
    setIsCapturing(false);
    setIsProcessing(true);
    setError(null);
    setResult(null);

    // Create blob URL for preview
    const imageToProcess = croppedFile || file;
    const imageUrl = URL.createObjectURL(imageToProcess);
    setReceiptImageUrl(imageUrl);

    try {
      const { extractedFields, rawText, confidence } = await extractReceiptFromImage(imageToProcess);

      // Attempt station matching from merchant name (non-blocking)
      let matchedStation: MatchedStation | null = null;
      const merchantName = extractedFields.merchantName.value;
      if (merchantName && String(merchantName).trim()) {
        matchedStation = await matchStationFromMerchant(String(merchantName));
      }

      const mappedFields = mapFieldsToFuelLog(extractedFields, matchedStation);

      setResult({
        extractedFields,
        mappedFields,
        rawText,
        overallConfidence: confidence,
        matchedStation,
      });
    } catch (err: any) {
      console.error('Receipt OCR processing failed:', err);
      const message = err.response?.data?.message || err.message || 'Failed to process receipt image';
      setError(message);
      // Clean up image URL on error
      URL.revokeObjectURL(imageUrl);
      setReceiptImageUrl(null);
    } finally {
      setIsProcessing(false);
    }
  }, []);

  const updateField = useCallback((
    fieldName: keyof ExtractedReceiptFields,
    value: string | number | null
  ) => {
    setResult((prev) => {
      if (!prev) return null;

      const updatedFields = {
        ...prev.extractedFields,
        [fieldName]: {
          ...prev.extractedFields[fieldName],
          value,
          confidence: 1.0, // User-edited field has full confidence
        },
      };

      // Clear matched station if merchant name was edited (user override)
      const station = fieldName === 'merchantName' ? null : prev.matchedStation;

      return {
        ...prev,
        extractedFields: updatedFields,
        mappedFields: mapFieldsToFuelLog(updatedFields, station),
        matchedStation: station,
      };
    });
  }, []);

  const acceptResult = useCallback(() => {
    if (!result) return null;

    const mappedFields = result.mappedFields;

    // Clean up
    if (receiptImageUrl) {
      URL.revokeObjectURL(receiptImageUrl);
    }
    setResult(null);
    setReceiptImageUrl(null);

    return mappedFields;
  }, [result, receiptImageUrl]);

  const clearMatchedStation = useCallback(() => {
    setResult((prev) => {
      if (!prev) return null;
      return {
        ...prev,
        matchedStation: null,
        mappedFields: mapFieldsToFuelLog(prev.extractedFields, null),
      };
    });
  }, []);

  const reset = useCallback(() => {
    setIsCapturing(false);
    setIsProcessing(false);
    if (receiptImageUrl) {
      URL.revokeObjectURL(receiptImageUrl);
    }
    setResult(null);
    setReceiptImageUrl(null);
    setError(null);
  }, [receiptImageUrl]);

  return {
    isCapturing,
    isProcessing,
    result,
    receiptImageUrl,
    error,
    startCapture,
    cancelCapture,
    processImage,
    acceptResult,
    reset,
    updateField,
    clearMatchedStation,
  };
}
