// 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, NgZone } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { Capacitor } from '@capacitor/core';
import { App } from '@capacitor/app';
import { NativeSettings, AndroidSettings, IOSSettings } from 'capacitor-native-settings';
import { Contacts } from '@capacitor-community/contacts';

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

/**
 * ContactsService - Gestione rubrica dispositivo (WhatsApp-like)
 *
 * Legge i contatti dal telefono LOCALMENTE: nessun dato viene inviato al server.
 * Sostituisce nome/cognome degli utenti quando lo username (numero di telefono) combacia.
 *
 * Flusso corretto:
 *   1. Utente apre Messaggi → modale personalizzata
 *   2. Accetta modale → requestAndLoad() → dialog Android → permesso concesso
 *   3. Avvii successivi: loadIfPermitted() carica silenziosamente (solo se già concesso)
 */
@Injectable({ providedIn: 'root' })
export class ContactsService {
	private readonly ASKED_KEY = 'contacts_permission_asked';

	/**
	 * Flag separato: true solo quando requestPermissions() ha restituito 'granted'.
	 * Usato per decidere se chiamare getContacts() automaticamente all'avvio.
	 * Questo evita che il dialog Android appaia senza che l'utente abbia interagito
	 * con la modale di consenso nell'app.
	 */
	private readonly GRANTED_KEY = 'contacts_permission_granted';

	/** Mappa normalizzata: phoneDigits → displayName */
	private phoneMap: Map<string, string> = new Map();
	private _count = 0;

	/** Guard di rientranza per evitare chiamate concorrenti a getContacts() */
	private isLoadingContacts = false;

	private statusSubject = new BehaviorSubject<ContactsPermissionStatus>('idle');
	status$ = this.statusSubject.asObservable();

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

	constructor(private ngZone: NgZone) {
		this.initStatus();
		this.listenForAppResume();
	}

	/** Imposta lo stato iniziale senza chiamare API native */
	private initStatus(): void {
		if (!this.isNative) return;
		if (this.wasGranted()) {
			// L'utente ha già concesso il permesso in precedenza — status ottimistico
			this.ngZone.run(() => this.statusSubject.next('granted'));
		}
	}

	/**
	 * Quando l'app torna in foreground, ricarica i contatti solo se il permesso
	 * era già stato esplicitamente concesso tramite requestPermissions().
	 * NON chiama requestPermissions() — evita dialog imprevisti.
	 */
	private listenForAppResume(): void {
		if (!this.isNative) return;
		App.addListener('appStateChange', async ({ isActive }) => {
			if (!isActive) return;
			if (!this.wasGranted()) return; // non chiedere mai senza consenso esplicito
			if (this.phoneMap.size > 0) return; // già caricati
			if (this.statusSubject.value === 'loading') return; // in corso
			await this.loadContacts();
		});
	}

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

	getCount(): number {
		return this._count;
	}

	hasBeenAsked(): boolean {
		return localStorage.getItem(this.ASKED_KEY) === 'true';
	}

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

	wasGranted(): boolean {
		return localStorage.getItem(this.GRANTED_KEY) === 'true';
	}

	private markAsGranted(): void {
		localStorage.setItem(this.GRANTED_KEY, 'true');
	}

	/**
	 * Chiede il permesso di sistema e carica la rubrica.
	 * Deve essere chiamato SOLO dopo che l'utente ha accettato la modale personalizzata.
	 * Ritorna true se i contatti sono stati caricati con successo.
	 */
	async requestAndLoad(): Promise<boolean> {
		if (!this.isNative) {
			this.markAsAsked();
			return false;
		}

		this.ngZone.run(() => this.statusSubject.next('loading'));
		this.markAsAsked();

		try {
			const perm = await Contacts.requestPermissions();
			if (perm.contacts !== 'granted') {
				this.ngZone.run(() => this.statusSubject.next('denied'));
				return false;
			}
			this.markAsGranted(); // salva che il permesso è stato concesso esplicitamente
			await this.loadContacts();
			return true;
		} catch {
			this.ngZone.run(() => this.statusSubject.next('denied'));
			return false;
		}
	}

	/**
	 * Carica i contatti silenziosamente se il permesso era già stato concesso in precedenza.
	 * NON mostra alcun dialog — chiamato all'avvio app e in ionViewWillEnter.
	 */
	async loadIfPermitted(): Promise<void> {
		if (!this.isNative) return;
		if (!this.wasGranted()) return; // mai senza consenso esplicito precedente
		if (this.phoneMap.size > 0) return; // già caricati
		await this.loadContacts();
	}

	/**
	 * Disabilita localmente i contatti: svuota la mappa, cancella il flag granted
	 * e resetta lo stato. Il permesso Android rimane concesso — l'utente può
	 * riabilitare dalla modale in Messaggi o dal profilo.
	 */
	disableContacts(): void {
		this.phoneMap = new Map();
		this._count = 0;
		localStorage.removeItem(this.GRANTED_KEY);
		localStorage.removeItem(this.ASKED_KEY);
		this.ngZone.run(() => this.statusSubject.next('idle'));
	}

	/**
	 * Apre le impostazioni di sistema. Resetta lo stato a 'idle' così al rientro
	 * l'utente può premere "Abilita" che chiamerà requestAndLoad() senza dialog.
	 */
	async openSystemSettings(): Promise<void> {
		this.ngZone.run(() => this.statusSubject.next('idle'));
		await NativeSettings.open({
			optionAndroid: AndroidSettings.ApplicationDetails,
			optionIOS: IOSSettings.App,
		});
	}

	private async loadContacts(): Promise<void> {
		if (this.isLoadingContacts) return;
		this.isLoadingContacts = true;
		this.ngZone.run(() => this.statusSubject.next('loading'));
		try {
			const result = await Contacts.getContacts({
				projection: { name: true, phones: true },
			});

			const newMap = new Map<string, string>();
			for (const contact of result.contacts) {
				const displayName = contact.name?.display || [contact.name?.given, contact.name?.family].filter(Boolean).join(' ');
				if (!displayName) continue;
				for (const phone of contact.phones || []) {
					if (!phone.number) continue;
					const normalized = this.normalizePhone(phone.number);
					if (normalized.length >= 6) newMap.set(normalized, displayName);
				}
			}

			this.ngZone.run(() => {
				this.phoneMap = newMap;
				this._count = newMap.size;
				this.statusSubject.next('granted');
			});
		} catch {
			// getContacts() fallito: il permesso potrebbe essere stato revocato
			localStorage.removeItem(this.GRANTED_KEY);
			this.ngZone.run(() => this.statusSubject.next('denied'));
		} finally {
			this.isLoadingContacts = false;
		}
	}

	getNameByPhone(phone: string | null | undefined): string | null {
		if (!phone || this.phoneMap.size === 0) return null;
		const normalized = this.normalizePhone(phone);
		if (!normalized) return null;
		if (this.phoneMap.has(normalized)) return this.phoneMap.get(normalized)!;
		if (normalized.length >= 8) {
			const suffix = normalized.slice(-8);
			for (const [key, name] of this.phoneMap) {
				if (key.endsWith(suffix)) return name;
			}
		}
		return null;
	}

	private normalizePhone(phone: string): string {
		return phone.replace(/\D/g, '');
	}

	isLoaded(): boolean {
		return this.statusSubject.value === 'granted';
	}
}
