/**
 * GPS-Tracking-Service.
 *
 * Wenn aktiv: pushed alle paar Sekunden die aktuelle Position als
 * `location_update {lat, lon}` an den RVS-Server, damit Brain-Watcher
 * mit `near()`-Conditions etwas zum Vergleichen haben.
 *
 * Default: AUS. Wird entweder vom User manuell in Settings angeschaltet
 * oder von ARIA via location_tracking-RVS-Message (Brain-Tool
 * `request_location_tracking`).
 *
 * Energie-Schutz: distanceFilter 30m, interval 15s. Echte Fahrt-Updates
 * (Geschwindigkeit) kommen sauber durch, stationaer wird kaum gesendet.
 */

import AsyncStorage from '@react-native-async-storage/async-storage';
import { Linking, PermissionsAndroid, Platform, ToastAndroid } from 'react-native';
import Geolocation from '@react-native-community/geolocation';
import rvs from './rvs';
import { acquireBackgroundAudio, releaseBackgroundAudio } from './backgroundAudio';

// Opt-in Background-GPS — Settings-Toggle "GPS auch im Hintergrund".
// Default AUS. Wenn AN: ACCESS_BACKGROUND_LOCATION-Permission noetig
// (kann nicht ueber Standard-Dialog angefordert werden, User muss in
// Android-Settings auf "Immer erlauben" gehen) + ForegroundService mit
// foregroundServiceType=location wird hochgezogen.
export const BG_GPS_STORAGE_KEY = 'aria_gps_background_enabled';

export async function isBackgroundGpsEnabled(): Promise<boolean> {
  try {
    const v = await AsyncStorage.getItem(BG_GPS_STORAGE_KEY);
    return v === 'true';
  } catch {
    return false;
  }
}

export async function setBackgroundGpsEnabled(enabled: boolean): Promise<void> {
  try {
    await AsyncStorage.setItem(BG_GPS_STORAGE_KEY, String(enabled));
  } catch {}
}

/** Prueft ob ACCESS_BACKGROUND_LOCATION gewaehrt ist und oeffnet sonst die
 *  Android-App-Settings damit der User "Immer erlauben" auswaehlen kann.
 *  Returns true wenn permission ok, false wenn User Settings oeffnen muss. */
export async function ensureBackgroundLocationPermission(): Promise<boolean> {
  if (Platform.OS !== 'android') return true;
  try {
    const granted = await PermissionsAndroid.check(
      'android.permission.ACCESS_BACKGROUND_LOCATION' as any,
    );
    if (granted) return true;
    // Erst FINE_LOCATION anfordern falls noch nicht da
    const fine = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
    );
    if (fine !== PermissionsAndroid.RESULTS.GRANTED) return false;
    // Ab Android 10+ kann BACKGROUND_LOCATION NICHT ueber den normalen
    // PermissionsAndroid.request abgefragt werden — User muss in Settings
    // auf "Immer erlauben" wechseln. Wir oeffnen die App-Settings-Seite.
    ToastAndroid.show(
      'Bitte in Android-Einstellungen unter Standort "Immer erlauben" auswaehlen',
      ToastAndroid.LONG,
    );
    Linking.openSettings();
    return false;
  } catch (e) {
    console.warn('[gps-track] BG-Permission-Check fehlgeschlagen:', e);
    return false;
  }
}

type Listener = (active: boolean) => void;

class GpsTrackingService {
  private watchId: number | null = null;
  private active = false;
  private listeners: Set<Listener> = new Set();
  // Defensive: nicht zu schnell oeffentlich togglen
  private lastChangeAt = 0;
  // Letzte bekannte Position — wird vom Heartbeat-Timer alle 60s erneut
  // an die Bridge gesendet, sonst veraltet near() im Brain (NEAR_MAX_AGE_SEC
  // = 5 min) wenn der User stationaer ist und distanceFilter keine Updates
  // mehr triggert.
  private lastLat: number | null = null;
  private lastLon: number | null = null;
  private heartbeatTimer: ReturnType<typeof setInterval> | null = null;

  isActive(): boolean {
    return this.active;
  }

  onChange(cb: Listener): () => void {
    this.listeners.add(cb);
    return () => { this.listeners.delete(cb); };
  }

  private notify() {
    for (const cb of this.listeners) {
      try { cb(this.active); } catch {}
    }
  }

  /** Beim App-Start: gespeicherten Zustand wiederherstellen (Default off). */
  async restoreFromStorage(): Promise<void> {
    try {
      const v = await AsyncStorage.getItem('aria_gps_tracking');
      if (v === 'true') {
        console.log('[gps-track] Restore: war an, starte wieder');
        this.start('Beim Start wiederhergestellt');
      }
    } catch {}
  }

  private async ensurePermission(): Promise<boolean> {
    if (Platform.OS !== 'android') return true;
    try {
      const granted = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
        {
          title: 'GPS-Tracking',
          message: 'ARIA braucht laufende Standort-Updates damit GPS-Watcher (Blitzer-Warner, near()) funktionieren.',
          buttonPositive: 'Erlauben',
          buttonNegative: 'Abbrechen',
        },
      );
      return granted === PermissionsAndroid.RESULTS.GRANTED;
    } catch (e) {
      console.warn('[gps-track] Permission-Fehler:', e);
      return false;
    }
  }

  async start(reason: string = ''): Promise<boolean> {
    if (this.active) return true;
    const ok = await this.ensurePermission();
    if (!ok) {
      ToastAndroid.show('GPS-Tracking: Berechtigung abgelehnt', ToastAndroid.LONG);
      return false;
    }
    // Background-GPS opt-in: wenn aktiv, ForegroundService mit type=location
    // hochziehen. Brauche ACCESS_BACKGROUND_LOCATION (User muss in Android-
    // Settings 'Immer erlauben' aktivieren). Wenn die fehlt, watchPosition
    // liefert im Hintergrund keine Updates (nur Heartbeat sendet alte Werte).
    const bgEnabled = await isBackgroundGpsEnabled();
    if (bgEnabled) {
      try { await acquireBackgroundAudio('location'); } catch {}
    }
    try {
      this.watchId = Geolocation.watchPosition(
        (pos) => {
          const lat = pos.coords.latitude;
          const lon = pos.coords.longitude;
          this.lastLat = lat;
          this.lastLon = lon;
          rvs.send('location_update' as any, { lat, lon });
        },
        (err) => {
          console.warn('[gps-track] watchPosition error:', err?.code, err?.message);
        },
        {
          enableHighAccuracy: true,
          distanceFilter: 30,     // erst senden wenn 30m gewandert
          interval: 15000,        // (Android) gewuenschte Frequenz
          fastestInterval: 10000, // (Android) max Frequenz
        } as any,
      );
      // Heartbeat: alle 60s die letzte bekannte Position erneut senden.
      // Sonst bleibt der Brain-State stale wenn der User stationaer ist
      // (distanceFilter blockt watchPosition-Updates) → near()-Watcher
      // verwerfen die Position als veraltet (NEAR_MAX_AGE_SEC = 300s).
      // Kein neuer GPS-Wakeup, nur Re-Send der letzten Werte → akkufreundlich.
      if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = setInterval(() => {
        if (this.lastLat != null && this.lastLon != null) {
          rvs.send('location_update' as any, { lat: this.lastLat, lon: this.lastLon });
        }
      }, 60_000);
      this.active = true;
      this.lastChangeAt = Date.now();
      this.notify();
      AsyncStorage.setItem('aria_gps_tracking', 'true').catch(() => {});
      ToastAndroid.show(
        reason ? `GPS-Tracking aktiv (${reason})` : 'GPS-Tracking aktiv',
        ToastAndroid.SHORT,
      );
      console.log('[gps-track] gestartet', reason ? `(${reason})` : '');
      return true;
    } catch (e: any) {
      console.warn('[gps-track] start fehlgeschlagen:', e?.message);
      return false;
    }
  }

  stop(reason: string = ''): void {
    if (!this.active) return;
    if (this.watchId !== null) {
      try { Geolocation.clearWatch(this.watchId); } catch {}
      this.watchId = null;
    }
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
    // Location-Foreground-Service-Slot freigeben (falls vorher acquired)
    try { releaseBackgroundAudio('location'); } catch {}
    this.active = false;
    this.lastChangeAt = Date.now();
    this.notify();
    AsyncStorage.setItem('aria_gps_tracking', 'false').catch(() => {});
    ToastAndroid.show(
      reason ? `GPS-Tracking aus (${reason})` : 'GPS-Tracking aus',
      ToastAndroid.SHORT,
    );
    console.log('[gps-track] gestoppt', reason ? `(${reason})` : '');
  }

  async toggle(reason: string = ''): Promise<void> {
    if (this.active) this.stop(reason);
    else await this.start(reason);
  }
}

export default new GpsTrackingService();
