/**
 * Excursion Service — Take a Walk / Take a Ride (Bekky, 2026-09-03).
 *
 * Tracks a walk or ride in the BACKGROUND (Option B — phone can lock / app
 * closed) via expo-location background location updates. The route polyline +
 * distance accumulate in a persisted ExcursionState so a killed app can
 * resume or offer to finalize the trip. On stop, builds a FieldAlbum carrying
 * the route + distance + duration + excursionId, which the caller adds to the
 * field journal. Walk XOR ride (mutual exclusivity — only one excursion at a
 * time). GPS temp override: users on rough/skip day-to-day can allow precise
 * tracking just for the excursion, reverting after.
 *
 * The Android-required foreground-service notification is self-contained in
 * expo-location (it builds its own NotificationChannel) — no expo-notifications
 * needed. Verified: LocationTaskService is baked into the installed APK.
 */
import * as TaskManager from 'expo-task-manager';
import * as Location from 'expo-location';

import type { ExcursionMode, ExcursionState, FieldAlbum, GpsPrecision } from '../../types/garden';
import { loadExcursionState, saveExcursionState, loadAppSettings, saveAppSettings } from '../gardenStorage';

export const EXCURSION_TASK = 'pixiesprout-excursion';

/**
 * GPS temp override (Bekky, 2026-09-03): users on rough/skip day-to-day can
 * allow precise tracking just for the excursion, reverting after. The prior
 * gpsPrecision is stored on the persisted ExcursionState (gpsOverridePrior) so
 * a killed app can still revert on finalize. applyGpsOverride() sets precise
 * for the excursion; revertGpsOverride() restores the prior value.
 */
async function applyGpsOverride(state: ExcursionState): Promise<ExcursionState> {
  const settings = await loadAppSettings();
  const prior = settings.privacy.gpsPrecision ?? 'rough';
  // Only override if the user is NOT already on precise (no need to change).
  if (prior === 'precise') return state;
  await saveAppSettings({
    ...settings,
    privacy: { ...settings.privacy, gpsPrecision: 'precise' },
  });
  return { ...state, gpsOverridePrior: prior };
}

async function revertGpsOverride(state: ExcursionState | null): Promise<void> {
  const prior = state?.gpsOverridePrior;
  if (prior == null) return;
  const settings = await loadAppSettings();
  await saveAppSettings({
    ...settings,
    privacy: { ...settings.privacy, gpsPrecision: prior },
  });
}

/** Haversine distance between two lat/lng points, in meters. */
export function haversineMeters(
  a: { latitude: number; longitude: number },
  b: { latitude: number; longitude: number }
): number {
  const R = 6371000; // Earth radius in meters
  const toRad = (deg: number) => (deg * Math.PI) / 180;
  const dLat = toRad(b.latitude - a.latitude);
  const dLng = toRad(b.longitude - a.longitude);
  const lat1 = toRad(a.latitude);
  const lat2 = toRad(b.latitude);
  const h =
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLng / 2) * Math.sin(dLng / 2);
  return 2 * R * Math.asin(Math.sqrt(h));
}

/** The background task: append each GPS point + accumulate distance. */
TaskManager.defineTask(EXCURSION_TASK, async ({ data, error }: any) => {
  try {
    if (error) {
      // Always log (release builds DO emit console.log to logcat — the
      // __DEV__ gate would hide this on-device).
      console.log('[Pixie-excursion] task error', JSON.stringify(error));
      return;
    }
    const locations = data?.locations as Location.LocationObject[] | undefined;
    console.log('[Pixie-excursion] task fired, locations=', locations?.length ?? 0);
    if (!locations || locations.length === 0) return;

    const state = await loadExcursionState();
    if (!state) {
      console.log('[Pixie-excursion] no active excursion state, skipping');
      return; // no active excursion — ignore stray updates
    }

    let points = [...state.points];
    let distance = state.distanceMeters;
    for (const loc of locations) {
      const { latitude, longitude } = loc.coords;
      const point = { latitude, longitude, timestamp: new Date(loc.timestamp).toISOString() };
      const last = points[points.length - 1];
      if (last) {
        distance += haversineMeters(last, point);
      }
      points.push(point);
    }
    // Cap the in-memory route to avoid unbounded growth on very long trips.
    if (points.length > 20000) {
      points = points.slice(points.length - 20000);
    }
    await saveExcursionState({ ...state, points, distanceMeters: distance });
    console.log(`[Pixie-excursion] saved dist=${distance.toFixed(1)}m points=${points.length}`);
  } catch (e) {
    // Always log so we can diagnose on-device release builds.
    console.log('[Pixie-excursion] task failed', e instanceof Error ? e.message : String(e));
  }
});

/** Is an excursion currently active? */
export async function getActiveExcursion(): Promise<ExcursionState | null> {
  return loadExcursionState();
}

/**
 * Start an excursion. Throws if one is already active (mutual exclusivity).
 * Requests location permission (foreground + background) and starts the
 * background location updates with the foreground-service notification.
 */
export async function startExcursion(mode: ExcursionMode): Promise<ExcursionState> {
  const existing = await loadExcursionState();
  if (existing) {
    throw new Error('An excursion is already active. End it before starting another.');
  }

  // Request permissions. Foreground is required; background enables tracking
  // with the phone locked. If the user denies, we surface a friendly message
  // in the UI (the feature can't work without location). If they've denied
  // permanently (canAskAgain=false), the UI offers a shortcut to the exact
  // system settings screen.
  const fg = await Location.requestForegroundPermissionsAsync();
  if (!fg.granted) {
    throw new Error(fg.canAskAgain === false ? 'location-denied-permanent' : 'location-denied');
  }
  const bg = await Location.requestBackgroundPermissionsAsync();
  if (!bg.granted) {
    throw new Error(bg.canAskAgain === false ? 'location-denied-permanent' : 'location-denied');
  }

  const now = new Date().toISOString();
  let state: ExcursionState = {
    mode,
    startedAt: now,
    points: [],
    distanceMeters: 0,
    excursionId: `excursion_${Date.now()}`,
  };
  // GPS temp override: if the user is on rough/skip day-to-day, allow precise
  // just for this excursion (reverts on stop). Honesty: we only override when
  // the user consented to start tracking.
  state = await applyGpsOverride(state);
  await saveExcursionState(state);

  const isWalk = mode === 'walk';
  await Location.startLocationUpdatesAsync(EXCURSION_TASK, {
    accuracy: Location.Accuracy.High,
    distanceInterval: 5,
    timeInterval: 10000,
    showsBackgroundLocationIndicator: true,
    foregroundService: {
      notificationTitle: isWalk ? 'Pixie is tracking your walk' : 'Pixie is tracking your ride',
      notificationBody: isWalk
        ? 'Your walk is being recorded. Tap the button in the app to end it and see your map.'
        : 'Your ride is being recorded. Tap the button in the app to end it and see your map.',
      notificationColor: '#2E6B3F',
      killServiceOnDestroy: false,
    },
  });

  return state;
}

/**
 * Stop the active excursion and build a FieldAlbum from the recorded route.
 * Returns the album (with route/distance/duration/excursionId) for the caller
 * to add to fieldAlbums. Clears the persisted state.
 */
export async function stopExcursion(): Promise<FieldAlbum> {
  const state = await loadExcursionState();
  if (!state) {
    throw new Error('No active excursion to end.');
  }

  try {
    await Location.stopLocationUpdatesAsync(EXCURSION_TASK);
  } catch (e) {
    // Best-effort — the task may already be stopped.
    if (typeof __DEV__ !== 'undefined' && __DEV__) {
      console.log('[Pixie-diag] stopLocationUpdatesAsync', e);
    }
  }

  const endedAt = new Date().toISOString();
  const durationSec = Math.max(0, Math.round((Date.parse(endedAt) - Date.parse(state.startedAt)) / 1000));
  const placeLabel = state.mode === 'walk' ? 'My Walk' : 'My Ride';

  const album: FieldAlbum = {
    id: `field_album_${Date.now()}`,
    albumKey: `${state.startedAt.slice(0, 10)}__${placeLabel}`,
    placeLabel,
    createdAt: state.startedAt,
    entries: [],
    count: 0,
    excursionMode: state.mode,
    route: state.points,
    distanceMeters: state.distanceMeters,
    durationSec,
    startedAt: state.startedAt,
    endedAt,
    excursionId: state.excursionId,
  };

  // Revert the GPS temp override BEFORE clearing the state (the prior value
  // lives on the state). Honesty: precise tracking was only for the excursion.
  await revertGpsOverride(state);

  await saveExcursionState(null);
  return album;
}

/** Format a duration in seconds as "H:MM:SS" or "MM:SS". */
export function formatExcursionDuration(sec: number): string {
  const s = Math.max(0, Math.floor(sec));
  const h = Math.floor(s / 3600);
  const m = Math.floor((s % 3600) / 60);
  const r = s % 60;
  const mm = String(m).padStart(2, '0');
  const ss = String(r).padStart(2, '0');
  return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}`;
}

/** Format a distance in meters as a friendly "2.1 km" / "350 m". */
export function formatExcursionDistance(meters: number): string {
  if (meters >= 1000) {
    return `${(meters / 1000).toFixed(1)} km`;
  }
  return `${Math.round(meters)} m`;
}
