import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import {
	BaseMatrixService,
	MatrixBotConfig,
	MatrixRoomEvent,
	UserListMapper,
	KeywordCommandDetector,
	COMMON_KEYWORDS,
} from '@manacore/matrix-bot-common';
import { PresiService, Deck, Theme, SlideContent } from '../presi/presi.service';
import {
	SessionService,
	TranscriptionService,
	CreditService,
	LOGIN_MESSAGES,
} from '@manacore/bot-services';
import { HELP_MESSAGE } from '../config/configuration';

@Injectable()
export class MatrixService extends BaseMatrixService {
	// User list mappers for number-based reference
	private decksMapper = new UserListMapper<Deck>();
	private themesMapper = new UserListMapper<Theme>();

	private readonly keywordDetector = new KeywordCommandDetector([
		...COMMON_KEYWORDS,
		{ keywords: ['presis', 'decks', 'praesentationen', 'liste'], command: 'presis' },
		{ keywords: ['folien', 'slides', 'folie hinzufuegen'], command: 'folie' },
		{ keywords: ['themes', 'designs', 'vorlagen', 'stile'], command: 'themes' },
		{ keywords: ['teilen', 'share', 'freigeben', 'link'], command: 'teilen' },
		{ keywords: ['links', 'shares', 'freigaben', 'geteilte'], command: 'links' },
		{ keywords: ['neu', 'new', 'neue praesentation', 'erstellen'], command: 'neu' },
	]);

	constructor(
		configService: ConfigService,
		private readonly transcriptionService: TranscriptionService,
		private presiService: PresiService,
		private sessionService: SessionService,
		private creditService: CreditService
	) {
		super(configService);
	}

	protected override async handleAudioMessage(
		roomId: string,
		event: MatrixRoomEvent,
		sender: string
	): Promise<void> {
		try {
			const mxcUrl = event.content.url;
			if (!mxcUrl) return;

			const audioBuffer = await this.downloadMedia(mxcUrl);
			const text = await this.transcriptionService.transcribe(audioBuffer);
			if (!text) {
				await this.sendMessage(roomId, '<p>❌ Sprachnachricht konnte nicht erkannt werden.</p>');
				return;
			}

			await this.sendMessage(roomId, `<p>🎤 <em>"${text}"</em></p>`);
			await this.handleTextMessage(roomId, event, text);
		} catch (error) {
			this.logger.error(`Audio transcription error: ${error}`);
			await this.sendMessage(roomId, '<p>❌ Fehler bei der Spracherkennung.</p>');
		}
	}

	protected getConfig(): MatrixBotConfig {
		return {
			homeserverUrl:
				this.configService.get<string>('matrix.homeserverUrl') || 'http://localhost:8008',
			accessToken: this.configService.get<string>('matrix.accessToken') || '',
			storagePath:
				this.configService.get<string>('matrix.storagePath') || './data/bot-storage.json',
			allowedRooms: this.configService.get<string[]>('matrix.allowedRooms') || [],
		};
	}

	protected async handleTextMessage(
		roomId: string,
		event: MatrixRoomEvent,
		body: string
	): Promise<void> {
		// Check for keyword commands first
		const keywordCommand = this.keywordDetector.detect(body);
		if (keywordCommand) {
			body = `!${keywordCommand}`;
		}

		if (!body.startsWith('!')) return;

		const sender = event.sender;
		const parts = body.slice(1).split(/\s+/);
		const command = parts[0].toLowerCase();
		const args = parts.slice(1);
		const argString = args.join(' ');

		try {
			switch (command) {
				case 'help':
				case 'hilfe':
					await this.sendMessage(roomId, HELP_MESSAGE);
					break;

				case 'status':
					await this.handleStatus(roomId, sender);
					break;

				// Deck commands
				case 'presis':
				case 'decks':
				case 'liste':
					await this.handleListDecks(roomId, sender);
					break;

				case 'presi':
				case 'deck':
				case 'details':
					await this.handleDeckDetails(roomId, sender, args[0]);
					break;

				case 'neu':
				case 'new':
				case 'create':
					await this.handleCreateDeck(roomId, sender, argString);
					break;

				case 'loeschen':
				case 'delete':
					await this.handleDeleteDeck(roomId, sender, args[0]);
					break;

				case 'umbenennen':
				case 'rename':
					await this.handleRenameDeck(roomId, sender, args[0], args.slice(1).join(' '));
					break;

				// Slide commands
				case 'folie':
				case 'slide':
					await this.handleAddSlide(roomId, sender, args);
					break;

				case 'folieloeschen':
				case 'deleteslide':
					await this.handleDeleteSlide(roomId, sender, args[0], args[1]);
					break;

				// Theme commands
				case 'themes':
				case 'designs':
					await this.handleListThemes(roomId, sender);
					break;

				case 'theme':
				case 'design':
					await this.handleApplyTheme(roomId, sender, args[0], args[1]);
					break;

				// Share commands
				case 'teilen':
				case 'share':
					await this.handleShareDeck(roomId, sender, argString);
					break;

				case 'links':
				case 'shares':
					await this.handleListShares(roomId, sender, args[0]);
					break;

				default:
					await this.sendMessage(
						roomId,
						`<p>Unbekannter Befehl: <code>${command}</code>. Nutze <code>!help</code> fuer Hilfe.</p>`
					);
			}
		} catch (error) {
			this.logger.error(`Error handling command ${command}:`, error);
			await this.sendMessage(roomId, `<p>Fehler: ${(error as Error).message}</p>`);
		}
	}

	private async requireAuth(sender: string): Promise<string> {
		const token = await this.sessionService.getToken(sender);
		if (!token) {
			throw new Error(LOGIN_MESSAGES.presi);
		}
		return token;
	}

	private async handleStatus(roomId: string, sender: string) {
		const backendOk = await this.presiService.checkHealth();
		const loggedIn = await this.sessionService.isLoggedIn(sender);
		const sessions = await this.sessionService.getSessionCount();
		const session = await this.sessionService.getSession(sender);
		const token = await this.sessionService.getToken(sender);

		let statusHtml = '<h3>Presi Bot Status</h3><ul>';
		statusHtml += `<li>Backend: ${backendOk ? 'Online' : 'Offline'}</li>`;
		statusHtml += `<li>Angemeldet: ${loggedIn ? 'Ja' : 'Nein'}</li>`;

		if (loggedIn && session && token) {
			const balance = await this.creditService.getBalance(token);
			statusHtml += `<li>👤 Angemeldet als: ${session.email}</li>`;
			statusHtml += `<li>⚡ Credits: ${balance.balance.toFixed(2)}</li>`;
		}

		statusHtml += `<li>Aktive Sessions: ${sessions}</li>`;
		statusHtml += '</ul>';

		await this.sendMessage(roomId, statusHtml);
	}

	// Deck handlers
	private async handleListDecks(roomId: string, sender: string) {
		const token = await this.requireAuth(sender);
		const result = await this.presiService.getDecks(token);

		if (result.error) {
			await this.sendMessage(roomId, `<p>Fehler: ${result.error}</p>`);
			return;
		}

		const decks = result.data || [];
		this.decksMapper.setList(sender, decks);

		if (decks.length === 0) {
			await this.sendMessage(
				roomId,
				'<p>Keine Praesentationen vorhanden. Erstelle eine mit <code>!neu Titel</code></p>'
			);
			return;
		}

		let html = '<h3>Deine Praesentationen</h3><ol>';
		for (const deck of decks) {
			const theme = deck.theme ? ` [${deck.theme.name}]` : '';
			const pub = deck.isPublic ? ' &#127760;' : '';
			html += `<li><strong>${deck.title}</strong>${theme}${pub}</li>`;
		}
		html += '</ol>';
		html += '<p><em>Nutze <code>!presi [nr]</code> fuer Details</em></p>';

		await this.sendMessage(roomId, html);
	}

	private async handleDeckDetails(roomId: string, sender: string, numberStr: string) {
		const token = await this.requireAuth(sender);
		const number = parseInt(numberStr, 10);
		const deck = this.decksMapper.getByNumber(sender, number);

		if (!deck) {
			await this.sendMessage(roomId, '<p>Ungueltige Nummer. Nutze zuerst <code>!presis</code></p>');
			return;
		}

		const result = await this.presiService.getDeck(token, deck.id);
		if (result.error) {
			await this.sendMessage(roomId, `<p>Fehler: ${result.error}</p>`);
			return;
		}

		const d = result.data!;
		let html = `<h3>${d.title}</h3>`;
		if (d.description) html += `<p><em>${d.description}</em></p>`;

		html += '<ul>';
		if (d.theme) html += `<li>Theme: ${d.theme.name}</li>`;
		html += `<li>Oeffentlich: ${d.isPublic ? 'Ja' : 'Nein'}</li>`;
		html += `<li>Folien: ${d.slides?.length || 0}</li>`;
		html += `<li>Erstellt: ${new Date(d.createdAt).toLocaleDateString('de-DE')}</li>`;
		html += '</ul>';

		if (d.slides && d.slides.length > 0) {
			html += '<p><strong>Folien:</strong></p><ol>';
			for (const slide of d.slides) {
				const title =
					slide.content.title || slide.content.body?.substring(0, 30) || `(${slide.content.type})`;
				html += `<li>${title}</li>`;
			}
			html += '</ol>';
		}

		html += `<p><em>Nutze <code>!folie ${numberStr} typ Inhalt</code> um Folien hinzuzufuegen</em></p>`;

		await this.sendMessage(roomId, html);
	}

	private async handleCreateDeck(roomId: string, sender: string, input: string) {
		if (!input) {
			await this.sendMessage(roomId, '<p>Verwendung: <code>!neu Titel | Beschreibung</code></p>');
			return;
		}

		const token = await this.requireAuth(sender);
		const parts = input.split('|').map((s) => s.trim());
		const title = parts[0];
		const description = parts[1];

		const result = await this.presiService.createDeck(token, title, description);

		if (result.error) {
			await this.sendMessage(roomId, `<p>Fehler: ${result.error}</p>`);
			return;
		}

		this.decksMapper.clearList(sender);
		await this.sendMessage(
			roomId,
			`<p>Praesentation <strong>${result.data!.title}</strong> erstellt!</p>
<p><em>Nutze <code>!presis</code> und dann <code>!folie [nr] typ Inhalt</code></em></p>`
		);
	}

	private async handleDeleteDeck(roomId: string, sender: string, numberStr: string) {
		const token = await this.requireAuth(sender);
		const number = parseInt(numberStr, 10);
		const deck = this.decksMapper.getByNumber(sender, number);

		if (!deck) {
			await this.sendMessage(roomId, '<p>Ungueltige Nummer. Nutze zuerst <code>!presis</code></p>');
			return;
		}

		const result = await this.presiService.deleteDeck(token, deck.id);

		if (result.error) {
			await this.sendMessage(roomId, `<p>Fehler: ${result.error}</p>`);
			return;
		}

		this.decksMapper.clearList(sender);
		await this.sendMessage(
			roomId,
			`<p>Praesentation <strong>${deck.title}</strong> geloescht.</p>`
		);
	}

	private async handleRenameDeck(
		roomId: string,
		sender: string,
		numberStr: string,
		newTitle: string
	) {
		if (!newTitle) {
			await this.sendMessage(
				roomId,
				'<p>Verwendung: <code>!umbenennen [nr] Neuer Titel</code></p>'
			);
			return;
		}

		const token = await this.requireAuth(sender);
		const number = parseInt(numberStr, 10);
		const deck = this.decksMapper.getByNumber(sender, number);

		if (!deck) {
			await this.sendMessage(roomId, '<p>Ungueltige Nummer. Nutze zuerst <code>!presis</code></p>');
			return;
		}

		const result = await this.presiService.updateDeck(token, deck.id, { title: newTitle });

		if (result.error) {
			await this.sendMessage(roomId, `<p>Fehler: ${result.error}</p>`);
			return;
		}

		await this.sendMessage(
			roomId,
			`<p><strong>${deck.title}</strong> umbenannt zu <strong>${newTitle}</strong></p>`
		);
	}

	// Slide handlers
	private async handleAddSlide(roomId: string, sender: string, args: string[]) {
		if (args.length < 2) {
			await this.sendMessage(
				roomId,
				`<p>Verwendung:</p>
<ul>
<li><code>!folie [nr] titel Titel | Untertitel</code></li>
<li><code>!folie [nr] text Titel | Inhalt</code></li>
<li><code>!folie [nr] punkte Titel | Punkt1, Punkt2</code></li>
</ul>`
			);
			return;
		}

		const token = await this.requireAuth(sender);
		const number = parseInt(args[0], 10);
		const deck = this.decksMapper.getByNumber(sender, number);

		if (!deck) {
			await this.sendMessage(roomId, '<p>Ungueltige Nummer. Nutze zuerst <code>!presis</code></p>');
			return;
		}

		const slideType = args[1].toLowerCase();
		const contentStr = args.slice(2).join(' ');
		const contentParts = contentStr.split('|').map((s) => s.trim());

		let content: SlideContent;

		switch (slideType) {
			case 'titel':
			case 'title':
				content = {
					type: 'title',
					title: contentParts[0] || 'Titel',
					subtitle: contentParts[1],
				};
				break;

			case 'text':
			case 'content':
			case 'inhalt':
				content = {
					type: 'content',
					title: contentParts[0] || 'Inhalt',
					body: contentParts[1] || '',
				};
				break;

			case 'punkte':
			case 'bullets':
			case 'liste':
				const bullets = contentParts[1]?.split(',').map((s) => s.trim()) || [];
				content = {
					type: 'content',
					title: contentParts[0] || 'Punkte',
					bulletPoints: bullets,
				};
				break;

			case 'bild':
			case 'image':
				content = {
					type: 'image',
					title: contentParts[0],
					imageUrl: contentParts[1],
				};
				break;

			default:
				await this.sendMessage(
					roomId,
					'<p>Unbekannter Folien-Typ. Verfuegbar: titel, text, punkte, bild</p>'
				);
				return;
		}

		const result = await this.presiService.addSlide(token, deck.id, content);

		if (result.error) {
			await this.sendMessage(roomId, `<p>Fehler: ${result.error}</p>`);
			return;
		}

		await this.sendMessage(
			roomId,
			`<p>Folie zu <strong>${deck.title}</strong> hinzugefuegt (Position ${result.data!.order + 1})</p>`
		);
	}

	private async handleDeleteSlide(
		roomId: string,
		sender: string,
		deckNumStr: string,
		slideNumStr: string
	) {
		if (!deckNumStr || !slideNumStr) {
			await this.sendMessage(
				roomId,
				'<p>Verwendung: <code>!folieloeschen [presi-nr] [folien-nr]</code></p>'
			);
			return;
		}

		const token = await this.requireAuth(sender);
		const deckNumber = parseInt(deckNumStr, 10);
		const deck = this.decksMapper.getByNumber(sender, deckNumber);

		if (!deck) {
			await this.sendMessage(roomId, '<p>Ungueltige Praesentation-Nummer.</p>');
			return;
		}

		// Get deck with slides
		const deckResult = await this.presiService.getDeck(token, deck.id);
		if (deckResult.error || !deckResult.data?.slides) {
			await this.sendMessage(roomId, `<p>Fehler: ${deckResult.error || 'Keine Folien'}</p>`);
			return;
		}

		const slideIndex = parseInt(slideNumStr, 10) - 1;
		if (isNaN(slideIndex) || slideIndex < 0 || slideIndex >= deckResult.data.slides.length) {
			await this.sendMessage(roomId, '<p>Ungueltige Folien-Nummer.</p>');
			return;
		}

		const slide = deckResult.data.slides[slideIndex];
		const result = await this.presiService.deleteSlide(token, slide.id);

		if (result.error) {
			await this.sendMessage(roomId, `<p>Fehler: ${result.error}</p>`);
			return;
		}

		await this.sendMessage(
			roomId,
			`<p>Folie ${slideNumStr} aus <strong>${deck.title}</strong> geloescht.</p>`
		);
	}

	// Theme handlers
	private async handleListThemes(roomId: string, sender: string) {
		const result = await this.presiService.getThemes();

		if (result.error) {
			await this.sendMessage(roomId, `<p>Fehler: ${result.error}</p>`);
			return;
		}

		const themes = result.data || [];
		this.themesMapper.setList(sender, themes);

		if (themes.length === 0) {
			await this.sendMessage(roomId, '<p>Keine Themes verfuegbar.</p>');
			return;
		}

		let html = '<h3>Verfuegbare Themes</h3><ol>';
		for (const theme of themes) {
			const def = theme.isDefault ? ' (Standard)' : '';
			html += `<li><strong>${theme.name}</strong>${def}</li>`;
		}
		html += '</ol>';
		html += '<p><em>Nutze <code>!theme [presi-nr] [theme-nr]</code></em></p>';

		await this.sendMessage(roomId, html);
	}

	private async handleApplyTheme(
		roomId: string,
		sender: string,
		deckNumStr: string,
		themeNumStr: string
	) {
		if (!deckNumStr || !themeNumStr) {
			await this.sendMessage(
				roomId,
				'<p>Verwendung: <code>!theme [presi-nr] [theme-nr]</code></p>'
			);
			return;
		}

		const token = await this.requireAuth(sender);
		const deckNumber = parseInt(deckNumStr, 10);
		const themeNumber = parseInt(themeNumStr, 10);
		const deck = this.decksMapper.getByNumber(sender, deckNumber);
		const theme = this.themesMapper.getByNumber(sender, themeNumber);

		if (!deck) {
			await this.sendMessage(roomId, '<p>Ungueltige Praesentation-Nummer.</p>');
			return;
		}

		if (!theme) {
			await this.sendMessage(
				roomId,
				'<p>Ungueltige Theme-Nummer. Nutze zuerst <code>!themes</code></p>'
			);
			return;
		}

		const result = await this.presiService.updateDeck(token, deck.id, { themeId: theme.id });

		if (result.error) {
			await this.sendMessage(roomId, `<p>Fehler: ${result.error}</p>`);
			return;
		}

		await this.sendMessage(
			roomId,
			`<p>Theme <strong>${theme.name}</strong> auf <strong>${deck.title}</strong> angewendet.</p>`
		);
	}

	// Share handlers
	private async handleShareDeck(roomId: string, sender: string, argString: string) {
		const args = argString.split(/\s+/);
		const numberStr = args[0];

		const token = await this.requireAuth(sender);
		const number = parseInt(numberStr, 10);
		const deck = this.decksMapper.getByNumber(sender, number);

		if (!deck) {
			await this.sendMessage(roomId, '<p>Ungueltige Nummer. Nutze zuerst <code>!presis</code></p>');
			return;
		}

		let expiresAt: string | undefined;

		// Parse --tage N
		const daysMatch = argString.match(/--tage\s+(\d+)/i);
		if (daysMatch) {
			const days = parseInt(daysMatch[1], 10);
			const expDate = new Date();
			expDate.setDate(expDate.getDate() + days);
			expiresAt = expDate.toISOString();
		}

		const result = await this.presiService.createShareLink(token, deck.id, expiresAt);

		if (result.error) {
			await this.sendMessage(roomId, `<p>Fehler: ${result.error}</p>`);
			return;
		}

		const shareUrl = this.presiService.getShareUrl(result.data!.shareCode);
		let html = `<p><strong>${deck.title}</strong> wird geteilt:</p>`;
		html += `<p><a href="${shareUrl}">${shareUrl}</a></p>`;
		if (result.data!.expiresAt) {
			html += `<p><em>Gueltig bis: ${new Date(result.data!.expiresAt).toLocaleDateString('de-DE')}</em></p>`;
		}

		await this.sendMessage(roomId, html);
	}

	private async handleListShares(roomId: string, sender: string, numberStr: string) {
		if (!numberStr) {
			await this.sendMessage(roomId, '<p>Verwendung: <code>!links [presi-nr]</code></p>');
			return;
		}

		const token = await this.requireAuth(sender);
		const number = parseInt(numberStr, 10);
		const deck = this.decksMapper.getByNumber(sender, number);

		if (!deck) {
			await this.sendMessage(roomId, '<p>Ungueltige Nummer. Nutze zuerst <code>!presis</code></p>');
			return;
		}

		const result = await this.presiService.getShareLinks(token, deck.id);

		if (result.error) {
			await this.sendMessage(roomId, `<p>Fehler: ${result.error}</p>`);
			return;
		}

		const links = result.data || [];

		if (links.length === 0) {
			await this.sendMessage(
				roomId,
				`<p>Keine Share-Links fuer <strong>${deck.title}</strong>. Nutze <code>!teilen ${numberStr}</code></p>`
			);
			return;
		}

		let html = `<h3>Share-Links: ${deck.title}</h3><ol>`;
		for (const link of links) {
			const expires = link.expiresAt
				? ` (bis ${new Date(link.expiresAt).toLocaleDateString('de-DE')})`
				: ' (unbegrenzt)';
			const url = this.presiService.getShareUrl(link.shareCode);
			html += `<li><a href="${url}">${link.shareCode}</a>${expires}</li>`;
		}
		html += '</ol>';

		await this.sendMessage(roomId, html);
	}
}
