// 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 { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ActionSheetController, AlertController, IonContent } from '@ionic/angular';

import { ConfigService } from 'src/app/providers/config.service';
import { MessagingService } from 'src/app/providers/messaging.service';
import { LayoutService } from 'src/app/providers/layout.service';
import { ContactsService } from 'src/app/providers/contacts.service';
import { CmpMessage, CmpAttachment, CmpConversationMember, UserSearchResult } from 'src/models/messaging/conversation.model';

@Component({
	standalone: false,
	selector: 'app-messaging-chat',
	templateUrl: './messaging-chat.page.html',
	styleUrls: ['./messaging-chat.page.scss'],
})
export class MessagingChatPage implements OnInit, OnDestroy {
	@ViewChild(IonContent) content!: IonContent;
	@ViewChild('fileInput') fileInput!: ElementRef<HTMLInputElement>;

	conv_id: number = 0;
	messages: CmpMessage[] = [];
	loading: boolean = false;
	currentUserId: number = 0;

	newMessage: string = '';
	sending: boolean = false;

	// Cache object URL degli allegati per att_id (evita ri-download a ogni refresh)
	private attUrlCache = new Map<number, string>();
	private attPending = new Set<number>();
	private loadInFlight = false;

	// Identity of the chat partner
	chatWithName: string = 'Chat';
	chatIsCompany: boolean = false;
	chatIsGroup: boolean = false;
	chatCmpId: number | null = null;
	chatPrtId: number | null = null;
	chatOtherUsername: string | null = null;

	// Gestione gruppo
	showGroupModal = false;
	groupMembers: CmpConversationMember[] = [];
	isGroupAdmin = false;
	memberSearchText = '';
	memberResults: UserSearchResult[] = [];
	searchingMembers = false;
	private memberSearchTimer: any;

	private refreshInterval: any;

	constructor(
		private route: ActivatedRoute,
		private configService: ConfigService,
		private messagingService: MessagingService,
		private layoutService: LayoutService,
		private actionSheetCtrl: ActionSheetController,
		private contactsService: ContactsService,
		private alertCtrl: AlertController,
		private router: Router,
	) {}

	ngOnInit(): void {
		this.conv_id = Number(this.route.snapshot.paramMap.get('conv_id'));
		this.currentUserId = this.configService.getUserID();

		// Pick up identity passed from the conversation list
		const state = history.state;
		this.chatIsCompany = state?.isCompany || false;
		this.chatIsGroup = state?.isGroup || false;
		this.chatCmpId = state?.cmpId || null;
		this.chatPrtId = state?.prtId || null;
		this.chatOtherUsername = state?.otherUsername || null;

		// Usa nome da rubrica se disponibile (solo per utenti non aziendali)
		const contactName = !this.chatIsCompany ? this.contactsService.getNameByPhone(this.chatOtherUsername) : null;
		this.chatWithName = contactName || state?.chatWith || 'Chat';

		const rightButton = this.chatIsCompany && this.chatCmpId && this.chatPrtId ? { icon: 'storefront-outline', route: `/portale/${this.chatPrtId}/company/${this.chatCmpId}/service/biz-appointments` } : null;

		this.layoutService.setPageConfig({ showBack: true, title: this.chatWithName, backButtonHref: '/messaging', rightButton });
		this.loadMessages();
		this.refreshInterval = setInterval(() => this.loadMessages(true), 15000);
	}

	ngOnDestroy(): void {
		if (this.refreshInterval) clearInterval(this.refreshInterval);
		this.attUrlCache.forEach((url) => URL.revokeObjectURL(url));
		this.attUrlCache.clear();
	}

	loadMessages(silent: boolean = false): void {
		// Su rete lenta il poll da 15s non deve accodare richieste sovrapposte:
		// una risposta vecchia arrivata per ultima sovrascriverebbe lo stato piu' recente.
		if (this.loadInFlight) return;
		this.loadInFlight = true;
		if (!silent) this.loading = true;
		this.messagingService.getMessages(this.conv_id).subscribe({
			next: (data: CmpMessage[]) => {
				const hadNew = data && data.length !== this.messages.length;
				this.messages = data || [];
				this.hydrateAttachments();
				this.updateTitleFromMessages();
				if (!silent || hadNew) {
					this.messagingService.markAsRead(this.conv_id).subscribe();
					setTimeout(() => this.scrollToBottom(), 100);
				}
			},
			error: () => {
				this.loadInFlight = false;
				this.loading = false;
			},
			complete: () => {
				this.loadInFlight = false;
				if (!silent) this.loading = false;
			},
		});
	}

	/**
	 * If the title wasn't set by navigation state, derive it from the first
	 * incoming message so the header always shows the correct partner name.
	 */
	private updateTitleFromMessages(): void {
		if (this.chatWithName && this.chatWithName !== 'Chat') return;
		const incoming = this.messages.find((m) => !this.isOwnMessage(m));
		if (!incoming) return;
		const contactName = !incoming.sender_company_name ? this.contactsService.getNameByPhone(incoming.sender_username) : null;
		const name = contactName || incoming.sender_company_name || [incoming.sender_firstname, incoming.sender_lastname].filter(Boolean).join(' ');
		if (name) {
			this.chatWithName = name;
			this.chatIsCompany = !!incoming.sender_company_name;
			this.layoutService.setPageConfig({ showBack: true, title: name, backButtonHref: '/messaging' });
		}
	}

	doRefresh(event: any): void {
		this.messagingService.getMessages(this.conv_id).subscribe({
			next: (data: CmpMessage[]) => {
				this.messages = data || [];
				this.hydrateAttachments();
				this.updateTitleFromMessages();
				this.messagingService.markAsRead(this.conv_id).subscribe();
				setTimeout(() => this.scrollToBottom(), 100);
			},
			error: () => event.target.complete(),
			complete: () => event.target.complete(),
		});
	}

	sendMessage(): void {
		if (!this.newMessage.trim() || this.sending) return;

		this.sending = true;
		const text = this.newMessage.trim();
		this.newMessage = '';

		this.messagingService.sendMessage(this.conv_id, text).subscribe({
			next: () => this.loadMessages(),
			error: () => {
				this.sending = false;
				this.configService.presentToastError('Errore invio messaggio');
				this.newMessage = text;
			},
			complete: () => {
				this.sending = false;
			},
		});
	}

	isOwnMessage(msg: CmpMessage): boolean {
		return msg.msg_sender_id === this.currentUserId;
	}

	isDeleted(msg: CmpMessage): boolean {
		return !!msg.msg_deleted_at;
	}

	async onOwnMessageTap(msg: CmpMessage): Promise<void> {
		if (msg.msg_deleted_at) return; // già eliminato
		const buttons: any[] = [];
		// Modifica solo se c'è testo (i messaggi di solo allegato non si modificano)
		if (msg.msg_text) {
			buttons.push({
				text: 'Modifica messaggio',
				icon: 'create-outline',
				handler: () => this.promptEditMessage(msg),
			});
		}
		buttons.push({
			text: 'Elimina messaggio',
			role: 'destructive',
			icon: 'trash-outline',
			handler: () => this.confirmDeleteMessage(msg),
		});
		buttons.push({ text: 'Annulla', role: 'cancel' });
		const sheet = await this.actionSheetCtrl.create({ buttons });
		await sheet.present();
	}

	private async promptEditMessage(msg: CmpMessage): Promise<void> {
		const alert = await this.alertCtrl.create({
			header: 'Modifica messaggio',
			inputs: [{ name: 'text', type: 'textarea', value: msg.msg_text }],
			buttons: [
				{ text: 'Annulla', role: 'cancel' },
				{
					text: 'Salva',
					handler: (data) => {
						const newText = (data.text || '').trim();
						if (!newText || newText === msg.msg_text) return;
						this.messagingService.editMessage(msg.msg_id, newText).subscribe({
							next: () => {
								msg.msg_text = newText;
								msg.msg_edited_at = new Date().toISOString();
							},
							error: () => this.configService.presentToastError('Errore modifica messaggio'),
						});
					},
				},
			],
		});
		await alert.present();
	}

	// --- Allegati ---

	triggerFilePicker(): void {
		this.fileInput?.nativeElement?.click();
	}

	onFilesPicked(event: Event): void {
		const input = event.target as HTMLInputElement;
		if (!input.files || input.files.length === 0) return;
		const files = Array.from(input.files);
		this.sending = true;
		this.messagingService.sendMedia(this.conv_id, this.newMessage.trim(), files).subscribe({
			next: () => {
				this.newMessage = '';
				this.loadMessages();
			},
			error: () => {
				this.sending = false;
				input.value = '';
				this.configService.presentToastError('Errore invio allegato');
			},
			complete: () => {
				this.sending = false;
				input.value = '';
			},
		});
	}

	private hydrateAttachments(): void {
		for (const msg of this.messages) {
			if (!msg.attachments) continue;
			for (const att of msg.attachments) {
				const cached = this.attUrlCache.get(att.att_id);
				if (cached) {
					att._objectUrl = cached;
					continue;
				}
				// Download gia' in corso (poll da 15s): non riaccodare la stessa richiesta
				if (this.attPending.has(att.att_id)) continue;
				this.attPending.add(att.att_id);
				this.messagingService.getAttachmentBlob(att.att_id).subscribe({
					next: (blob) => {
						this.attPending.delete(att.att_id);
						let url = this.attUrlCache.get(att.att_id);
						if (!url) {
							url = URL.createObjectURL(blob);
							this.attUrlCache.set(att.att_id, url);
						}
						att._objectUrl = url;
					},
					error: () => this.attPending.delete(att.att_id),
				});
			}
		}
	}

	isImage(att: CmpAttachment): boolean {
		return !!att.att_mime && att.att_mime.startsWith('image/');
	}

	openAttachment(att: CmpAttachment): void {
		if (att._objectUrl) window.open(att._objectUrl, '_blank');
	}

	isEdited(msg: CmpMessage): boolean {
		return !!msg.msg_edited_at;
	}

	// --- Gestione gruppo ---

	openGroupManager(): void {
		if (!this.chatIsGroup) return;
		this.memberSearchText = '';
		this.memberResults = [];
		this.loadGroupMembers();
		this.showGroupModal = true;
	}

	closeGroupManager(): void {
		this.showGroupModal = false;
		this.memberResults = [];
		this.memberSearchText = '';
	}

	private loadGroupMembers(): void {
		this.messagingService.getGroupMembers(this.conv_id).subscribe({
			next: (members) => {
				this.groupMembers = members || [];
				const me = this.groupMembers.find((m) => m.cvm_usr_id === this.currentUserId);
				this.isGroupAdmin = me?.cvm_role === 'ADMIN';
			},
		});
	}

	onMemberSearchInput(): void {
		if (this.memberSearchTimer) clearTimeout(this.memberSearchTimer);
		const txt = (this.memberSearchText || '').trim();
		this.memberSearchTimer = setTimeout(() => {
			this.searchingMembers = true;
			this.messagingService.searchGroupUsers(txt).subscribe({
				next: (res) => {
					const existing = new Set(this.groupMembers.map((m) => m.cvm_usr_id));
					this.memberResults = (res || []).filter((u) => !existing.has(u.usr_id));
				},
				complete: () => (this.searchingMembers = false),
				error: () => {
					this.searchingMembers = false;
					this.memberResults = [];
				},
			});
		}, 300);
	}

	addMember(user: UserSearchResult): void {
		this.messagingService.addGroupMember(this.conv_id, user.usr_id).subscribe({
			next: () => {
				this.memberResults = this.memberResults.filter((u) => u.usr_id !== user.usr_id);
				this.loadGroupMembers();
			},
			error: () => this.configService.presentToastError('Errore aggiunta membro'),
		});
	}

	removeMember(member: CmpConversationMember): void {
		this.messagingService.removeGroupMember(this.conv_id, member.cvm_usr_id).subscribe({
			next: () => this.loadGroupMembers(),
			error: () => this.configService.presentToastError('Errore rimozione membro'),
		});
	}

	async promptRenameGroup(): Promise<void> {
		const alert = await this.alertCtrl.create({
			header: 'Rinomina gruppo',
			inputs: [{ name: 'title', type: 'text', value: this.chatWithName }],
			buttons: [
				{ text: 'Annulla', role: 'cancel' },
				{
					text: 'Salva',
					handler: (data) => {
						const title = (data.title || '').trim();
						if (!title) return;
						this.messagingService.renameGroup(this.conv_id, title).subscribe({
							next: () => {
								this.chatWithName = title;
								this.layoutService.setPageConfig({ showBack: true, title, backButtonHref: '/messaging' });
							},
							error: () => this.configService.presentToastError('Errore rinomina gruppo'),
						});
					},
				},
			],
		});
		await alert.present();
	}

	async confirmLeaveGroup(): Promise<void> {
		const alert = await this.alertCtrl.create({
			header: 'Abbandona gruppo',
			message: 'Vuoi davvero abbandonare questo gruppo?',
			buttons: [
				{ text: 'Annulla', role: 'cancel' },
				{
					text: 'Abbandona',
					role: 'destructive',
					handler: () => {
						this.messagingService.leaveGroup(this.conv_id).subscribe({
							next: () => {
								this.closeGroupManager();
								this.router.navigate(['/messaging']);
							},
							error: () => this.configService.presentToastError('Errore'),
						});
					},
				},
			],
		});
		await alert.present();
	}

	memberName(m: CmpConversationMember): string {
		return [m.usr_firstname, m.usr_lastname].filter(Boolean).join(' ') || m.usr_username || 'Utente';
	}

	private confirmDeleteMessage(msg: CmpMessage): void {
		this.messagingService.deleteMessage(msg.msg_id).subscribe({
			next: () => {
				msg.msg_deleted_at = new Date().toISOString();
				msg.msg_text = '';
			},
			error: () => this.configService.presentToastError('Errore eliminazione messaggio'),
		});
	}

	/**
	 * Returns the sender display label for incoming messages.
	 * - If the sender is a company operator → show company name
	 * - If the sender's phone is in the device contacts → show contact name
	 * - Otherwise → show personal name from server
	 */
	getSenderLabel(msg: CmpMessage): string {
		if (msg.sender_company_name) return msg.sender_company_name;
		const contactName = this.contactsService.getNameByPhone(msg.sender_username);
		if (contactName) return contactName;
		return [msg.sender_firstname, msg.sender_lastname].filter(Boolean).join(' ') || 'Staff';
	}

	/**
	 * Returns initials for the incoming message avatar.
	 */
	getSenderInitials(msg: CmpMessage): string {
		const label = this.getSenderLabel(msg);
		return this.toInitials(label);
	}

	/** Returns initials for the chat-with header strip. */
	getChatWithInitials(): string {
		return this.toInitials(this.chatWithName);
	}

	private toInitials(label: string): string {
		const words = label.trim().split(/\s+/);
		if (words.length >= 2) return (words[0][0] + words[1][0]).toUpperCase();
		return label.substring(0, 2).toUpperCase();
	}

	formatTime(dateStr: string): string {
		if (!dateStr) return '';
		return new Date(dateStr).toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' });
	}

	shouldShowDate(index: number): boolean {
		if (index === 0) return true;
		const curr = new Date(this.messages[index].msg_created_at).toDateString();
		const prev = new Date(this.messages[index - 1].msg_created_at).toDateString();
		return curr !== prev;
	}

	getDateLabel(msg: CmpMessage): string {
		if (!msg.msg_created_at) return '';
		return new Date(msg.msg_created_at).toLocaleDateString('it-IT', { weekday: 'short', day: 'numeric', month: 'short' });
	}

	onKeyDown(event: KeyboardEvent): void {
		if (event.key === 'Enter' && !event.shiftKey) {
			event.preventDefault();
			this.sendMessage();
		}
	}

	private scrollToBottom(): void {
		if (this.content) this.content.scrollToBottom(300);
	}
}
