import React, { createContext, useContext, useState, useEffect, useCallback } from 'react';
import { useColorScheme, Appearance } from 'react-native';
import { getLocales } from 'expo-localization';
import {
  Plant,
  IdentificationResult,
  Language,
  AppearanceMode,
  AppColorScheme,
  ColorPalette,
} from '../types';
import { ImageCacheService } from '../services/imageCacheService';
import { getTranslation } from '../utils/translations';
import { backendApiClient } from '../services/backend/backendApiClient';
import {
  BillingSummary,
  PurchaseProductId,
  RevenueCatCustomerInfo,
  RevenueCatEntitlementInfo,
  RevenueCatSyncSource,
  SimulatedWebhookEvent,
} from '../services/backend/contracts';
import { createIdempotencyKey } from '../utils/idempotency';
import { AuthService, AuthSession } from '../services/authService';
import { Analytics } from '../services/analytics';
import { PlantsDb, SettingsDb, LexiconHistoryDb, AppMetaDb } from '../services/database';

interface AppState {
  session: AuthSession | null;
  plants: Plant[];
  language: Language;
  appearanceMode: AppearanceMode;
  colorPalette: ColorPalette;
  profileName: string;
  profileImageUri: string | null;
  billingSummary: BillingSummary | null;
  isActivatingEntitlement: boolean;
  resolvedScheme: AppColorScheme;
  isDarkMode: boolean;
  isInitializing: boolean;
  isLoadingPlants: boolean;
  isLoadingBilling: boolean;
  t: ReturnType<typeof getTranslation>;
  // Actions
  setAppearanceMode: (mode: AppearanceMode) => void;
  setColorPalette: (palette: ColorPalette) => void;
  setProfileName: (name: string) => Promise<void>;
  setProfileImage: (imageUri: string | null) => Promise<void>;
  changeLanguage: (lang: Language) => void;
  savePlant: (result: IdentificationResult, imageUri: string, overrideSession?: AuthSession) => Promise<void>;
  deletePlant: (id: string) => Promise<void>;
  updatePlant: (plant: Plant) => void;
  refreshPlants: () => void;
  refreshBillingSummary: () => Promise<BillingSummary | null>;
  syncRevenueCatState: (customerInfo: RevenueCatCustomerInfo, source?: RevenueCatSyncSource) => Promise<BillingSummary | null>;
  simulatePurchase: (productId: PurchaseProductId) => Promise<void>;
  simulateWebhookEvent: (event: SimulatedWebhookEvent, payload?: { credits?: number }) => Promise<void>;
  getLexiconSearchHistory: () => string[];
  saveLexiconSearchQuery: (query: string) => void;
  clearLexiconSearchHistory: () => void;
  hydrateSession: (session: AuthSession) => Promise<BillingSummary | null>;
  signOut: () => Promise<void>;
  setPendingPlant: (result: IdentificationResult, imageUri: string) => void;
  getPendingPlant: () => { result: IdentificationResult; imageUri: string } | null;
  guestScanCount: number;
  incrementGuestScanCount: () => void;
  hasCompletedOnboarding: boolean;
  markOnboardingCompleted: () => void;
}

const AppContext = createContext<AppState | null>(null);

const toErrorMessage = (error: unknown): string => {
  if (error instanceof Error) return error.message;
  return String(error);
};

export const useApp = () => {
  const ctx = useContext(AppContext);
  if (!ctx) throw new Error('useApp must be used within AppProvider');
  return ctx;
};

const isAppearanceMode = (v: string): v is AppearanceMode =>
  v === 'system' || v === 'light' || v === 'dark';
const isColorPalette = (v: string): v is ColorPalette =>
  v === 'forest' || v === 'ocean' || v === 'sunset' || v === 'mono';
const isLanguage = (v: string): v is Language => v === 'de' || v === 'en' || v === 'es';
const REVENUECAT_PRO_ENTITLEMENT_ID = (process.env.EXPO_PUBLIC_REVENUECAT_PRO_ENTITLEMENT_ID || 'pro').trim() || 'pro';
const SUPPORTED_REVENUECAT_SUBSCRIPTION_PRODUCTS = new Set<PurchaseProductId>(['weekly_pro', 'monthly_pro', 'yearly_pro']);

const summarizeRevenueCatCustomerInfo = (customerInfo: RevenueCatCustomerInfo) => {
  const activeEntitlements = customerInfo?.entitlements?.active || {};
  return {
    appUserId: customerInfo?.appUserId ?? null,
    originalAppUserId: customerInfo?.originalAppUserId ?? null,
    activeEntitlements: Object.entries(activeEntitlements).map(([id, entitlement]) => ({
      id,
      productIdentifier: entitlement?.productIdentifier ?? null,
      expirationDate: entitlement?.expirationDate || entitlement?.expiresDate || null,
    })),
    allPurchasedProductIdentifiers: customerInfo?.allPurchasedProductIdentifiers ?? [],
    nonSubscriptionTransactions: [
      ...(Array.isArray(customerInfo?.nonSubscriptionTransactions) ? customerInfo.nonSubscriptionTransactions : []),
      ...Object.values(customerInfo?.nonSubscriptions || {}).flatMap((entries) => (Array.isArray(entries) ? entries : [])),
    ].map((transaction) => ({
      productIdentifier: transaction?.productIdentifier ?? null,
      transactionIdentifier: transaction?.transactionIdentifier || transaction?.transactionId || null,
    })),
  };
};

const getValidProEntitlement = (customerInfo: RevenueCatCustomerInfo): RevenueCatEntitlementInfo | null => {
  const activeEntitlements = customerInfo?.entitlements?.active || {};
  const proEntitlement = activeEntitlements[REVENUECAT_PRO_ENTITLEMENT_ID];
  if (!proEntitlement) {
    return null;
  }

  if (proEntitlement.productIdentifier && SUPPORTED_REVENUECAT_SUBSCRIPTION_PRODUCTS.has(proEntitlement.productIdentifier as PurchaseProductId)) {
    return proEntitlement;
  }

  console.warn('[Billing] Ignoring unsupported RevenueCat pro entitlement during local sync', summarizeRevenueCatCustomerInfo(customerInfo));
  return null;
};

const getDeviceLanguage = (): Language => {
  try {
    const locale = getLocales()[0];
    const lang = (locale?.languageCode || locale?.languageTag || '').split('-')[0].toLowerCase();
    if (lang === 'de') return 'de';
    if (lang === 'es') return 'es';
    return 'en';
  } catch {
    return 'en';
  }
};

export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [systemColorScheme, setSystemColorScheme] = useState(Appearance.getColorScheme());

  useEffect(() => {
    const subscription = Appearance.addChangeListener(({ colorScheme }) => {
      setSystemColorScheme(colorScheme);
    });
    return () => subscription.remove();
  }, []);

  const [session, setSession] = useState<AuthSession | null>(null);
  const [plants, setPlants] = useState<Plant[]>([]);
  const [language, setLanguage] = useState<Language>(getDeviceLanguage());
  const [appearanceMode, setAppearanceModeState] = useState<AppearanceMode>('system');
  const [colorPalette, setColorPaletteState] = useState<ColorPalette>('forest');
  const [profileName, setProfileNameState] = useState('');
  const [profileImageUri, setProfileImageUri] = useState<string | null>(null);
  const [pendingPlant, setPendingPlantState] = useState<{ result: IdentificationResult; imageUri: string } | null>(null);
  const [guestScanCount, setGuestScanCount] = useState(0);
  const [hasCompletedOnboarding, setHasCompletedOnboarding] = useState(false);
  const [isInitializing, setIsInitializing] = useState(true);
  const [isLoadingPlants, setIsLoadingPlants] = useState(true);
  const [billingSummary, setBillingSummary] = useState<BillingSummary | null>(null);
  const [isLoadingBilling, setIsLoadingBilling] = useState(true);
  const [isActivatingEntitlement, setIsActivatingEntitlement] = useState(false);

  const resolvedScheme: AppColorScheme =
    appearanceMode === 'system'
      ? (systemColorScheme ?? 'dark') === 'dark' ? 'dark' : 'light'
      : appearanceMode;
  const isDarkMode = resolvedScheme === 'dark';
  const t = getTranslation(language);

  const refreshBillingSummary = useCallback(async (): Promise<BillingSummary | null> => {
    setIsLoadingBilling(true);
    try {
      const summary = await backendApiClient.getBillingSummary();
      setBillingSummary(summary);
      return summary;
    } catch (e) {
      console.error('Failed to refresh billing summary', e);
      // Transient failure: keep the last-known summary so a network blip
      // doesn't wipe a paying user's entitlement mid-session. Session
      // transitions (hydrateSession/sign-out) clear the summary themselves,
      // so a stale summary can never leak across accounts.
      return null;
    } finally {
      setIsLoadingBilling(false);
    }
  }, []);

  const resetStateForSignedOutUser = useCallback(() => {
    setSession(null);
    // Old account's billing must not survive into the guest/next session.
    setBillingSummary(null);
    setPlants([]);
    setLanguage(getDeviceLanguage());
    // Theme ist eine GERAETE-Einstellung, keine Kontodaten. Beim Ausloggen oder
    // beim Start ohne Session darf sie deshalb nicht auf die Defaults fallen -
    // sonst landet ein Gast nach jedem Neustart wieder auf Dark/Forest.
    const deviceAppearance = AppMetaDb.get('appearance_mode');
    setAppearanceModeState(deviceAppearance && isAppearanceMode(deviceAppearance) ? deviceAppearance : 'system');
    const devicePalette = AppMetaDb.get('color_palette');
    setColorPaletteState(devicePalette && isColorPalette(devicePalette) ? devicePalette : 'forest');
    setProfileNameState('');
    setProfileImageUri(null);
    setIsLoadingPlants(false);
    // Fetch guest billing summary instead of setting it to null
    refreshBillingSummary();
  }, [refreshBillingSummary]);

  const refreshPlants = useCallback(() => {
    if (!session) return;
    try {
      setPlants(PlantsDb.getAll(session.userId));
    } catch (error) {
      console.error('Failed to refresh plants list.', {
        userId: session.userId,
        error: toErrorMessage(error),
      });
    }
  }, [session]);

  const savePlant = useCallback(async (result: IdentificationResult, imageUri: string, overrideSession?: AuthSession) => {
    const activeSession = overrideSession || session;
    if (!activeSession) {
      console.warn('Ignoring savePlant request: no active user session.');
      return;
    }

    const now = new Date().toISOString();
    let finalImageUri = imageUri;

    try {
      finalImageUri = await ImageCacheService.cacheImage(imageUri);
    } catch (error) {
      console.error('Failed to cache plant image before save.', {
        userId: activeSession.userId,
        error: toErrorMessage(error),
      });
    }

    const newPlant: Plant = {
      id: Math.random().toString(36).substr(2, 9),
      name: result.name,
      botanicalName: result.botanicalName,
      imageUri: finalImageUri,
      dateAdded: now,
      careInfo: result.careInfo,
      lastWatered: now,
      wateringHistory: [now],
      description: result.description,
      notificationsEnabled: false,
    };

    try {
      PlantsDb.insert(activeSession.userId, newPlant);
    } catch (error) {
      console.error('Failed to insert plant into SQLite.', {
        userId: activeSession.userId,
        plantId: newPlant.id,
        plantName: newPlant.name,
        error: toErrorMessage(error),
      });
      throw error;
    }

    try {
      const reloadedPlants = PlantsDb.getAll(activeSession.userId);
      const insertedPlantExists = reloadedPlants.some((plant) => plant.id === newPlant.id);

      if (!insertedPlantExists) {
        console.warn('Plant was inserted but not found in immediate reload. Applying optimistic list update.', {
          userId: activeSession.userId,
          plantId: newPlant.id,
        });
        setPlants(prev => [newPlant, ...prev.filter((plant) => plant.id !== newPlant.id)]);
        return;
      }

      setPlants(reloadedPlants);
    } catch (error) {
      console.error('Failed to refresh plants after insert. Applying optimistic fallback.', {
        userId: activeSession.userId,
        plantId: newPlant.id,
        error: toErrorMessage(error),
      });
      setPlants(prev => [newPlant, ...prev.filter((plant) => plant.id !== newPlant.id)]);
    }
  }, [session]);

  const deletePlant = useCallback(async (id: string) => {
    if (!session) return;
    const plant = plants.find(p => p.id === id);
    if (plant?.imageUri) {
      await ImageCacheService.deleteCachedImage(plant.imageUri);
    }
    PlantsDb.delete(session.userId, id);
    setPlants(prev => prev.filter(p => p.id !== id));
  }, [session, plants]);

  const updatePlant = useCallback((updatedPlant: Plant) => {
    if (!session) return;
    PlantsDb.update(session.userId, updatedPlant);
    setPlants(prev => prev.map(p => p.id === updatedPlant.id ? updatedPlant : p));
  }, [session]);

  const hydrateSession = useCallback(async (nextSession: AuthSession) => {
    setSession(nextSession);
    setProfileNameState(nextSession.name);
    Analytics.identify(nextSession.serverUserId, {
      email: nextSession.email,
      name: nextSession.name,
    });
    setIsLoadingPlants(true);
    setIsLoadingBilling(true);
    // The previous (guest or prior account) summary is meaningless for this
    // session — clear it so the entitlement gate treats it as "unknown"
    // instead of bouncing on stale data if the fetch below fails.
    setBillingSummary(null);

    // Settings aus SQLite
    try {
      const settings = SettingsDb.get(nextSession.userId);
      if (settings.language_set === 1 && isLanguage(settings.language)) setLanguage(settings.language as Language);
      if (isAppearanceMode(settings.appearance_mode)) setAppearanceModeState(settings.appearance_mode as AppearanceMode);
      if (isColorPalette(settings.color_palette)) setColorPaletteState(settings.color_palette as ColorPalette);
      setProfileImageUri(settings.profile_image);
    } catch (e) {
      console.error('Failed to load settings from SQLite', e);
    }

    // Pflanzen laden
    try {
      setPlants(PlantsDb.getAll(nextSession.userId));
    } catch (error) {
      console.error('Failed to load plants during app bootstrap.', {
        userId: nextSession.userId,
        error: toErrorMessage(error),
      });
      setPlants([]);
    } finally {
      setIsLoadingPlants(false);
    }

    // Billing laden
    let summary = await refreshBillingSummary();
    if (!summary) {
      // Einmaliger Retry nach 2s
      setTimeout(() => {
        refreshBillingSummary();
      }, 2000);
    }

    // Check for pending plant to save after login/signup
    if (pendingPlant) {
      setTimeout(async () => {
        try {
          // Inside hydrateSession, the state 'session' might not be updated yet
          // but we can pass nextSession to savePlant if we modify it, 
          // but savePlant uses the 'session' from the outer scope.
          // However, by the time this timeout runs, the session state SHOULD be set.
          await savePlant(pendingPlant.result, pendingPlant.imageUri, nextSession);
          setPendingPlantState(null);
        } catch (e) {
          console.error('Failed to save pending plant after hydration', e);
        }
      }, 800);
    }

    return summary;
  }, [refreshBillingSummary, pendingPlant, savePlant]);

  const signOut = useCallback(async () => {
    await AuthService.logout();
    Analytics.reset();
    resetStateForSignedOutUser();
  }, [resetStateForSignedOutUser]);

  // Session + Settings laden (inkl. Server-Validierung)
  useEffect(() => {
    (async () => {
      try {
        // Load guest scan count from DB
        const savedCount = AppMetaDb.get('guest_scan_count');
        if (savedCount) {
          setGuestScanCount(parseInt(savedCount, 10) || 0);
        }

        if (AppMetaDb.get('onboarding_completed') === '1') {
          setHasCompletedOnboarding(true);
        }

        // Theme sofort aus dem Geraetespeicher setzen - vor Session-Restore und
        // vor dem Server-Roundtrip. Danach ueberschreibt hydrateSession ggf. mit
        // den nutzerspezifischen Werten, die aber identisch sein sollten.
        const storedAppearance = AppMetaDb.get('appearance_mode');
        if (storedAppearance && isAppearanceMode(storedAppearance)) {
          setAppearanceModeState(storedAppearance);
        }
        const storedPalette = AppMetaDb.get('color_palette');
        if (storedPalette && isColorPalette(storedPalette)) {
          setColorPaletteState(storedPalette);
        }

        const s = await AuthService.getSession();
        if (!s) {
          resetStateForSignedOutUser();
          return;
        }
        // Token validieren bevor Session gesetzt wird — verhindert kurzes Dashboard-Flash
        const validity = await AuthService.validateWithServer();
        if (validity === 'invalid') {
          await AuthService.logout();
          resetStateForSignedOutUser();
          return;
        }
        await hydrateSession(s);
      } catch (error) {
        console.error('Critical failure during AppContext initialization', error);
        resetStateForSignedOutUser();
      } finally {
        setIsInitializing(false);
      }
    })();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const applyRevenueCatCustomerInfoLocally = useCallback((
    customerInfo: RevenueCatCustomerInfo,
    source: RevenueCatSyncSource = 'app_init',
  ) => {
    if (source === 'topup_purchase') {
      return false;
    }

    const activeEntitlements = customerInfo?.entitlements?.active || {};
    const rawProEntitlement = activeEntitlements[REVENUECAT_PRO_ENTITLEMENT_ID];
    const proEntitlement = getValidProEntitlement(customerInfo);
    const isPro = Boolean(proEntitlement);
    const now = new Date();
    const renewsAt = proEntitlement?.expirationDate || proEntitlement?.expiresDate || null;
    const isTrial = (proEntitlement?.periodType || proEntitlement?.period_type || '').toUpperCase() === 'TRIAL';
    const monthlyAllowance = isTrial ? 30 : 100;

    setBillingSummary((prev) => {
      if (!proEntitlement && rawProEntitlement) {
        return prev;
      }

      if (!prev && isPro) {
        return {
          entitlement: {
            plan: 'pro',
            provider: 'revenuecat',
            status: 'active',
            renewsAt,
          },
          credits: {
            monthlyAllowance,
            usedThisCycle: 0,
            topupBalance: 0,
            available: monthlyAllowance,
            cycleStartedAt: now.toISOString(),
            cycleEndsAt: renewsAt || new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000).toISOString(),
          },
          availableProducts: ['weekly_pro', 'monthly_pro', 'yearly_pro', 'topup_small', 'topup_medium', 'topup_large'],
        };
      }

      if (!prev) return prev;

      return {
        ...prev,
        entitlement: {
          ...prev.entitlement,
          plan: isPro ? 'pro' : 'free',
          provider: 'revenuecat',
          status: isPro ? 'active' : 'inactive',
          renewsAt: proEntitlement?.expirationDate || proEntitlement?.expiresDate || null,
        },
      };
    });

    return isPro;
  }, []);

  const syncRevenueCatState = useCallback(async (
    customerInfo: RevenueCatCustomerInfo,
    source: RevenueCatSyncSource = 'app_init',
  ) => {
    console.log('[Billing] Syncing RevenueCat customer info', {
      source,
      customerInfo: summarizeRevenueCatCustomerInfo(customerInfo),
    });
    const didActivatePro = applyRevenueCatCustomerInfoLocally(customerInfo, source);
    const isSubscriptionActivation = source === 'subscription_purchase' && didActivatePro;
    if (isSubscriptionActivation) {
      setIsActivatingEntitlement(true);
    }
    try {
      const response = await backendApiClient.syncRevenueCatState({ customerInfo, source });
      setBillingSummary(response.billing);
      return response.billing;
    } catch (error) {
      console.error('Failed to sync RevenueCat state with backend', error);
      return null;
    } finally {
      if (isSubscriptionActivation) {
        setIsActivatingEntitlement(false);
      }
    }
  }, [applyRevenueCatCustomerInfoLocally]);

  const simulatePurchase = useCallback(async (productId: PurchaseProductId) => {
    const response = await backendApiClient.simulatePurchase({
      idempotencyKey: createIdempotencyKey('purchase', productId),
      productId,
    });
    setBillingSummary(response.billing);
  }, []);

  const simulateWebhookEvent = useCallback(async (
    event: SimulatedWebhookEvent,
    payload?: { credits?: number },
  ) => {
    const response = await backendApiClient.simulateWebhook({
      idempotencyKey: createIdempotencyKey('webhook', event),
      event,
      payload,
    });
    setBillingSummary(response.billing);
  }, []);

  const setAppearanceMode = useCallback((mode: AppearanceMode) => {
    setAppearanceModeState(mode);
    // Geraeteweit speichern, unabhaengig von einer Session: sonst geht die
    // Auswahl fuer Gaeste beim Neustart verloren, und eingeloggte Nutzer sehen
    // beim Start kurz das Default-Theme, weil die Nutzer-Settings erst nach
    // hydrateSession (inkl. Server-Roundtrip) geladen werden.
    AppMetaDb.set('appearance_mode', mode);
    if (session) SettingsDb.setAppearanceMode(session.userId, mode);
  }, [session]);

  const setColorPalette = useCallback((palette: ColorPalette) => {
    setColorPaletteState(palette);
    AppMetaDb.set('color_palette', palette);
    if (session) SettingsDb.setColorPalette(session.userId, palette);
  }, [session]);

  const setProfileName = useCallback(async (name: string) => {
    const normalized = name.trim() || session?.name || 'GreenLens User';
    setProfileNameState(normalized);
    if (session) {
      SettingsDb.setName(session.userId, normalized);
      await AuthService.updateSessionName(normalized);
    }
  }, [session]);

  const setProfileImage = useCallback(async (imageUri: string | null) => {
    let nextUri = imageUri;
    if (imageUri) {
      try {
        nextUri = await ImageCacheService.cacheImage(imageUri);
      } catch (e) {
        console.error('Failed to cache profile image', e);
      }
    }
    if (profileImageUri && profileImageUri !== nextUri) {
      await ImageCacheService.deleteCachedImage(profileImageUri);
    }
    setProfileImageUri(nextUri);
    if (session) SettingsDb.setProfileImage(session.userId, nextUri);
  }, [session, profileImageUri]);

  const changeLanguage = useCallback((lang: Language) => {
    setLanguage(lang);
    if (session) SettingsDb.setLanguage(session.userId, lang);
  }, [session]);


  // Lexicon history — synchron (SQLite sync API)
  const getLexiconSearchHistory = useCallback((): string[] => {
    if (!session) return [];
    return LexiconHistoryDb.getAll(session.userId);
  }, [session]);

  const saveLexiconSearchQuery = useCallback((query: string) => {
    if (!session) return;
    LexiconHistoryDb.add(session.userId, query);
  }, [session]);

  const clearLexiconSearchHistory = useCallback(() => {
    if (!session) return;
    LexiconHistoryDb.clear(session.userId);
  }, [session]);

  const setPendingPlant = useCallback((result: IdentificationResult, imageUri: string) => {
    setPendingPlantState({ result, imageUri });
  }, []);

  const getPendingPlant = useCallback(() => {
    return pendingPlant;
  }, [pendingPlant]);

  const incrementGuestScanCount = useCallback(() => {
    setGuestScanCount(prev => {
      const next = prev + 1;
      AppMetaDb.set('guest_scan_count', next.toString());
      return next;
    });
  }, []);

  const markOnboardingCompleted = useCallback(() => {
    AppMetaDb.set('onboarding_completed', '1');
    setHasCompletedOnboarding(true);
  }, []);

  return (
    <AppContext.Provider value={{
      session,
      plants,
      language,
      appearanceMode,
      colorPalette,
      profileName,
      profileImageUri,
      billingSummary,
      isActivatingEntitlement,
      resolvedScheme,
      isDarkMode,
      isInitializing,
      isLoadingPlants,
      isLoadingBilling,
      t,
      setAppearanceMode,
      setColorPalette,
      setProfileName,
      setProfileImage,
      changeLanguage,
      savePlant,
      deletePlant,
      updatePlant,
      refreshPlants,
      refreshBillingSummary,
      syncRevenueCatState,
      simulatePurchase,
      simulateWebhookEvent,
      getLexiconSearchHistory,
      saveLexiconSearchQuery,
      clearLexiconSearchHistory,
      hydrateSession,
      signOut,
      setPendingPlant,
      getPendingPlant,
      guestScanCount,
      incrementGuestScanCount,
      hasCompletedOnboarding,
      markOnboardingCompleted,
    }}>
      {children}
    </AppContext.Provider>
  );
};
