// Questo file fa parte di MyTown.
// MyTown è software libero: puoi ridistribuirlo e/o modificarlo secondo
// i termini della GNU Affero General Public License come pubblicata dalla
// Free Software Foundation, versione 3.
// Distribuito senza ALCUNA GARANZIA; vedi la GNU AGPL v3 per i dettagli.
// Copyright (C) Kondor Consulting & Services S.r.l.
// SPDX-License-Identifier: AGPL-3.0-or-later

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { Geolocation } from '@capacitor/geolocation';
import { Capacitor } from '@capacitor/core';
import { App } from '@capacitor/app';
import { NativeSettings, AndroidSettings, IOSSettings } from 'capacitor-native-settings';
import { Platform } from '@ionic/angular';

export type GeoPermissionStatus = 'idle' | 'loading' | 'granted' | 'denied';

export interface UserPosition {
	latitude: number;
	longitude: number;
	accuracy: number;
	timestamp: number;
}

@Injectable({ providedIn: 'root' })
export class GeolocationService {
	private readonly STORAGE_KEY = 'user_geolocation';
	private readonly ASKED_KEY = 'geolocation_asked';
	private readonly DISABLED_KEY = 'geolocation_disabled_by_user';

	private positionSubject = new BehaviorSubject<UserPosition | null>(this.loadFromStorage());
	private statusSubject = new BehaviorSubject<GeoPermissionStatus>('idle');

	position$ = this.positionSubject.asObservable();
	status$ = this.statusSubject.asObservable();

	private get isNative(): boolean {
		return Capacitor.isNativePlatform();
	}

	constructor(private platform: Platform) {
		this.initPermissionStatus();
		this.listenForAppResume();
	}

	private loadFromStorage(): UserPosition | null {
		try {
			const stored = localStorage.getItem(this.STORAGE_KEY);
			return stored ? JSON.parse(stored) : null;
		} catch {
			return null;
		}
	}

	private async initPermissionStatus(): Promise<void> {
		// Se abbiamo già una posizione salvata, partiamo da granted
		if (this.positionSubject.value) {
			this.statusSubject.next('granted');
		}

		try {
			if (this.isNative) {
				const perm = await Geolocation.checkPermissions();
				if (perm.location === 'granted') {
					this.statusSubject.next('granted');
					this.refreshPosition();
				} else if (perm.location === 'denied') {
					this.statusSubject.next('denied');
				}
			} else {
				// Web fallback
				if ('permissions' in navigator) {
					const perm = await (navigator as any).permissions.query({ name: 'geolocation' });
					if (perm.state === 'granted') {
						this.statusSubject.next('granted');
						this.refreshPositionWeb();
					} else if (perm.state === 'denied') {
						this.statusSubject.next('denied');
					}
					perm.addEventListener('change', () => {
						if (perm.state === 'granted') {
							this.statusSubject.next('granted');
							this.refreshPositionWeb();
						} else if (perm.state === 'denied') {
							this.statusSubject.next('denied');
							this.positionSubject.next(null);
							localStorage.removeItem(this.STORAGE_KEY);
						}
					});
				}
			}
		} catch {
			// Permissions API non disponibile
		}
	}

	/** Rileva il ritorno in foreground e ricontrolla i permessi */
	private listenForAppResume(): void {
		if (!this.isNative) return;
		App.addListener('appStateChange', async ({ isActive }) => {
			if (!isActive) return;
			// Non riabilitare mai se l'utente ha disabilitato intenzionalmente
			if (this.isDisabledByUser()) return;
			try {
				const perm = await Geolocation.checkPermissions();
				if (perm.location === 'granted' && this.statusSubject.value !== 'granted') {
					this.statusSubject.next('granted');
					this.markAsAsked();
					await this.refreshPosition();
				} else if (perm.location === 'denied' && this.statusSubject.value === 'loading') {
					this.statusSubject.next('denied');
				}
			} catch {}
		});
	}

	private isDisabledByUser(): boolean {
		return localStorage.getItem(this.DISABLED_KEY) === 'true';
	}

	/** Restituisce true se l'utente ha già risposto (sì o no) */
	hasBeenAsked(): boolean {
		return localStorage.getItem(this.ASKED_KEY) === 'true';
	}

	markAsAsked(): void {
		localStorage.setItem(this.ASKED_KEY, 'true');
	}

	/** Rimuove il flag "già chiesto" — usato da impostazioni per riproporre il permesso */
	resetAsked(): void {
		localStorage.removeItem(this.ASKED_KEY);
	}

	getPosition(): UserPosition | null {
		return this.positionSubject.value;
	}

	getStatus(): GeoPermissionStatus {
		return this.statusSubject.value;
	}

	/** Richiede la posizione GPS */
	requestPosition(): Promise<UserPosition> {
		this.statusSubject.next('loading');
		this.markAsAsked();
		localStorage.removeItem(this.DISABLED_KEY); // l'utente vuole abilitare

		if (this.isNative) {
			return this.requestPositionNative();
		}
		return this.requestPositionWeb();
	}

	private async requestPositionNative(): Promise<UserPosition> {
		try {
			// Richiede il permesso di sistema (mostra il dialog Android/iOS)
			const perm = await Geolocation.requestPermissions();
			if (perm.location !== 'granted') {
				this.statusSubject.next('denied');
				throw new Error('Permesso geolocalizzazione negato');
			}

			const position = await Geolocation.getCurrentPosition({
				enableHighAccuracy: false,
				timeout: 10000,
				maximumAge: 300000,
			});

			const pos: UserPosition = {
				latitude: position.coords.latitude,
				longitude: position.coords.longitude,
				accuracy: position.coords.accuracy,
				timestamp: position.timestamp,
			};
			localStorage.setItem(this.STORAGE_KEY, JSON.stringify(pos));
			this.positionSubject.next(pos);
			this.statusSubject.next('granted');
			return pos;
		} catch (err) {
			this.statusSubject.next('denied');
			throw err;
		}
	}

	private requestPositionWeb(): Promise<UserPosition> {
		return new Promise((resolve, reject) => {
			if (!('geolocation' in navigator)) {
				this.statusSubject.next('denied');
				reject(new Error('Geolocalizzazione non supportata'));
				return;
			}

			navigator.geolocation.getCurrentPosition(
				(position) => {
					const pos: UserPosition = {
						latitude: position.coords.latitude,
						longitude: position.coords.longitude,
						accuracy: position.coords.accuracy,
						timestamp: position.timestamp,
					};
					localStorage.setItem(this.STORAGE_KEY, JSON.stringify(pos));
					this.positionSubject.next(pos);
					this.statusSubject.next('granted');
					resolve(pos);
				},
				(error) => {
					this.statusSubject.next('denied');
					reject(error);
				},
				{ enableHighAccuracy: false, timeout: 10000, maximumAge: 300000 },
			);
		});
	}

	private async refreshPosition(): Promise<void> {
		try {
			const position = await Geolocation.getCurrentPosition({
				enableHighAccuracy: false,
				timeout: 10000,
				maximumAge: 300000,
			});
			const pos: UserPosition = {
				latitude: position.coords.latitude,
				longitude: position.coords.longitude,
				accuracy: position.coords.accuracy,
				timestamp: position.timestamp,
			};
			localStorage.setItem(this.STORAGE_KEY, JSON.stringify(pos));
			this.positionSubject.next(pos);
		} catch {}
	}

	private refreshPositionWeb(): void {
		navigator.geolocation.getCurrentPosition(
			(position) => {
				const pos: UserPosition = {
					latitude: position.coords.latitude,
					longitude: position.coords.longitude,
					accuracy: position.coords.accuracy,
					timestamp: position.timestamp,
				};
				localStorage.setItem(this.STORAGE_KEY, JSON.stringify(pos));
				this.positionSubject.next(pos);
			},
			() => {},
			{ enableHighAccuracy: false, timeout: 10000, maximumAge: 300000 },
		);
	}

	/** Apre le impostazioni di sistema per abilitare la posizione */
	async openSystemLocationSettings(): Promise<void> {
		await NativeSettings.open({
			optionAndroid: AndroidSettings.ApplicationDetails,
			optionIOS: IOSSettings.App,
		});
	}

	/** Disabilita la posizione e impedisce il ripristino automatico */
	clearPosition(): void {
		localStorage.removeItem(this.STORAGE_KEY);
		localStorage.setItem(this.DISABLED_KEY, 'true');
		this.positionSubject.next(null);
		this.statusSubject.next('idle');
		this.markAsAsked(); // non riproporre il popup
	}

	/**
	 * Calcola la distanza in km tra due coordinate (formula Haversine).
	 */
	calculateDistance(lat1: number, lng1: number, lat2: number, lng2: number): number {
		const R = 6371;
		const dLat = this.toRad(lat2 - lat1);
		const dLng = this.toRad(lng2 - lng1);
		const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.toRad(lat1)) * Math.cos(this.toRad(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2);
		const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
		return R * c;
	}

	/** Formatta la distanza in m o km */
	formatDistance(distanceKm: number): string {
		if (distanceKm < 1) return `${Math.round(distanceKm * 1000)} m`;
		return `${distanceKm.toFixed(1)} km`;
	}

	/**
	 * Calcola distanza dall'utente verso una sede aziendale.
	 * Restituisce null se la posizione non è disponibile o le coordinate sono 0,0.
	 */
	distanceFromUser(orgLatitude: number | undefined, orgLongitude: number | undefined): number | null {
		const pos = this.positionSubject.value;
		if (!pos || !orgLatitude || !orgLongitude || (orgLatitude === 0 && orgLongitude === 0)) return null;
		return this.calculateDistance(pos.latitude, pos.longitude, orgLatitude, orgLongitude);
	}

	private toRad(deg: number): number {
		return deg * (Math.PI / 180);
	}
}
