import * as vscode from 'vscode';
import axios, { AxiosError } from 'axios';
import * as path from 'path';

type Severity = 'info' | 'low' | 'medium' | 'high' | 'critical';

interface ApiFinding {
	check: string;
	title: string;
	severity: Severity;
	description: string;
	remediation?: string;
	line?: number;
	column?: number;
}

interface ApiSummary {
	total?: number;
	by_severity?: Record<Severity, number>;
}

interface SourceScanResponse {
	status?: string;
	scan_id?: string;
	timestamp?: string;
	findings?: ApiFinding[];
	summary?: ApiSummary;
	error?: string;
}

interface DomainScanResponse {
	status?: string;
	scan_id?: string;
	timestamp?: string;
	domain?: string;
	findings?: ApiFinding[];
	summary?: ApiSummary;
	error?: string;
}

interface LicenseVerificationResponse {
	valid: boolean;
	tier?: string;
	reason?: string;
	limits?: {
		daily?: number;
		monthly?: number;
	};
	metadata?: Record<string, string | null>;
	error?: string;
}

interface ExtensionConfiguration {
	apiUrl: string;
	apiKey: string | undefined;
	licenseEmail: string | undefined;
	licenseTier: string;
	autoScanOnSave: boolean;
	minimumSeverity: Severity;
}

const SUPPORTED_LANGUAGES = ['php', 'javascript', 'typescript', 'python', 'java', 'csharp', 'go'];
const severityWeights: Record<Severity, number> = {
	info: 0,
	low: 1,
	medium: 2,
	high: 3,
	critical: 4
};

function getConfiguration(): ExtensionConfiguration {
	const config = vscode.workspace.getConfiguration('webSentinel');
	return {
		apiUrl: config.get<string>('apiUrl', 'https://api.web-sentinel.taaazzz-prog.fr/api/v1'),
		apiKey: config.get<string>('apiKey') ?? undefined,
		licenseEmail: config.get<string>('licenseEmail') ?? undefined,
		licenseTier: config.get<string>('licenseTier', 'free'),
		autoScanOnSave: config.get<boolean>('autoScanOnSave', false),
		minimumSeverity: config.get<Severity>('minimumSeverity', 'medium')
	};
}

function ensureTrailingSlashRemoved(url: string): string {
	return url.endsWith('/') ? url.slice(0, -1) : url;
}

function normaliseEndpoint(baseUrl: string, endpoint: string): string {
	return `${ensureTrailingSlashRemoved(baseUrl)}${endpoint}`;
}

function severityPassesThreshold(severity: Severity, threshold: Severity): boolean {
	return severityWeights[severity] >= severityWeights[threshold];
}

function mapLanguage(languageId: string): string {
	const mapping: Record<string, string> = {
		php: 'php',
		javascript: 'javascript',
		typescript: 'typescript',
		python: 'python',
		java: 'java',
		csharp: 'csharp',
		go: 'go'
	};
	return mapping[languageId] ?? 'auto';
}

function pickFindings(data: { findings?: ApiFinding[] } | undefined): ApiFinding[] {
	if (!data) {
		return [];
	}
	if (Array.isArray(data.findings)) {
		return data.findings;
	}
	return [];
}

function isErrorPayload(value: unknown): value is { error?: string } {
	return Boolean(value && typeof value === 'object' && 'error' in value);
}

function describeAxiosError(error: AxiosError): string {
	if (error.response) {
		const status = error.response.status;
		const payload = error.response.data;
		const message = typeof payload === 'string'
			? payload
			: isErrorPayload(payload) && typeof payload.error === 'string'
				? payload.error
				: JSON.stringify(payload ?? {});
		return `API responded with status ${status}: ${message}`;
	}
	if (error.request) {
		return 'No response received from API.';
	}
	return error.message;
}

class WebSentinelDiagnostics {
	private readonly diagnostics = vscode.languages.createDiagnosticCollection('web-sentinel');

	dispose(): void {
		this.diagnostics.dispose();
	}

	clear(document?: vscode.TextDocument): void {
		if (document) {
			this.diagnostics.set(document.uri, []);
			return;
		}
		this.diagnostics.clear();
	}

	showFindings(document: vscode.TextDocument, findings: ApiFinding[], minSeverity: Severity): void {
		const diagnostics: vscode.Diagnostic[] = findings
			.filter((finding) => severityPassesThreshold(finding.severity, minSeverity))
			.map((finding) => {
				const line = Math.max(0, (finding.line ?? 1) - 1);
				const range = new vscode.Range(line, 0, line, 200);
				const diagnostic = new vscode.Diagnostic(range, `${finding.title}: ${finding.description}`, this.mapSeverity(finding.severity));
				diagnostic.source = 'Web Sentinel';
				diagnostic.code = finding.check;
				return diagnostic;
			});

		this.diagnostics.set(document.uri, diagnostics);
	}

	private mapSeverity(severity: Severity): vscode.DiagnosticSeverity {
		switch (severity) {
			case 'critical':
			case 'high':
				return vscode.DiagnosticSeverity.Error;
			case 'medium':
				return vscode.DiagnosticSeverity.Warning;
			case 'low':
			case 'info':
				return vscode.DiagnosticSeverity.Information;
			default:
				return vscode.DiagnosticSeverity.Warning;
		}
	}
}

class WebSentinelStatusBar {
	private readonly statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);

	constructor() {
		this.statusBarItem.text = '$(shield-check) Web Sentinel';
		this.statusBarItem.tooltip = 'Web Sentinel status';
		this.statusBarItem.command = 'web-sentinel.configureCredentials';
		this.statusBarItem.show();
	}

	update(text: string, tooltip?: string): void {
		this.statusBarItem.text = text;
		if (tooltip) {
			this.statusBarItem.tooltip = tooltip;
		}
	}

	dispose(): void {
		this.statusBarItem.dispose();
	}
}

class WebSentinelExtension {
	private readonly diagnostics = new WebSentinelDiagnostics();
	private readonly statusBar = new WebSentinelStatusBar();
	private readonly output = vscode.window.createOutputChannel('Web Sentinel');

	dispose(): void {
		this.diagnostics.dispose();
		this.statusBar.dispose();
		this.output.dispose();
	}

	async scanActiveFile(): Promise<void> {
		const editor = vscode.window.activeTextEditor;
		if (!editor) {
			void vscode.window.showWarningMessage('Aucun fichier actif à scanner.');
			return;
		}
		await this.scanDocument(editor.document);
	}

	async scanDocument(document: vscode.TextDocument): Promise<void> {
		const config = getConfiguration();
		if (!config.apiKey) {
			void vscode.window.showErrorMessage('Clé API Web Sentinel manquante. Configurez-la via la commande "Web Sentinel: Configure API Credentials".');
			return;
		}

		const languageId = mapLanguage(document.languageId);
		if (languageId === 'auto' && !SUPPORTED_LANGUAGES.includes(document.languageId)) {
			void vscode.window.showWarningMessage(`Langage ${document.languageId} non supporté pour le moment.`);
		}

		const baseUrl = ensureTrailingSlashRemoved(config.apiUrl);
		const endpoint = normaliseEndpoint(baseUrl, '/scan-source');

		this.statusBar.update('$(sync~spin) Web Sentinel: scan en cours...');

		try {
			const response = await axios.post<SourceScanResponse>(endpoint, {
				api_key: config.apiKey,
				source_content: document.getText(),
				file_path: document.fileName,
				language: languageId,
				options: {
					min_severity: config.minimumSeverity,
					detailed_report: true
				}
			}, {
				headers: {
					'Content-Type': 'application/json',
					'User-Agent': 'Web-Sentinel-VSCode/0.1.0'
				},
				timeout: 60000
			});

			const findings = pickFindings(response.data);
			this.diagnostics.showFindings(document, findings, config.minimumSeverity);
			this.output.appendLine(`[${new Date().toISOString()}] Scan fichier ${path.basename(document.fileName)} -> ${findings.length} trouvaille(s)`);

			const total = findings.length;
			this.statusBar.update(`$(shield) ${total} issues`, `${total} vulnérabilité(s) détectée(s)`);

			if (total === 0) {
				void vscode.window.showInformationMessage('Web Sentinel: aucune vulnérabilité détectée sur ce fichier.');
			} else {
				void vscode.window.showWarningMessage(`Web Sentinel: ${total} vulnérabilité(s) détectée(s). Consultez l'onglet "Problems".`);
			}
		} catch (error) {
			this.diagnostics.clear(document);
			const message = axios.isAxiosError(error) ? describeAxiosError(error) : (error as Error).message;
			this.statusBar.update('$(shield-x) Web Sentinel', message);
			this.output.appendLine(`[${new Date().toISOString()}] Échec du scan fichier: ${message}`);
			void vscode.window.showErrorMessage(`Web Sentinel: impossible d'exécuter le scan. ${message}`);
		}
	}

	async scanWorkspace(): Promise<void> {
		const workspaceFolders = vscode.workspace.workspaceFolders;
		if (!workspaceFolders?.length) {
			void vscode.window.showErrorMessage('Ouvrez un workspace pour lancer un scan global.');
			return;
		}

		const firstFolder = workspaceFolders[0];
		const files = await vscode.workspace.findFiles(new vscode.RelativePattern(firstFolder, '**/*.{php,js,ts,py,java,cs,go}'), '**/node_modules/**');

		if (files.length === 0) {
			void vscode.window.showInformationMessage('Aucun fichier compatible trouvé dans le workspace.');
			return;
		}

		const choice = await vscode.window.showInformationMessage(`Scanner ${files.length} fichier(s) avec Web Sentinel ?`, { modal: true }, 'Lancer');
		if (choice !== 'Lancer') {
			return;
		}

		await vscode.window.withProgress({
			title: 'Web Sentinel: scan du workspace',
			location: vscode.ProgressLocation.Notification,
			cancellable: true
		}, async (progress, token) => {
			for (let index = 0; index < files.length; index += 1) {
				if (token.isCancellationRequested) {
					break;
				}
				const uri = files[index];
				const document = await vscode.workspace.openTextDocument(uri);
				progress.report({
					message: `Scan ${index + 1}/${files.length}: ${path.basename(uri.fsPath)}`,
					increment: 100 / files.length
				});
				await this.scanDocument(document);
			}
		});
	}

	async scanDomain(): Promise<void> {
		const config = getConfiguration();
		if (!config.apiKey) {
			void vscode.window.showErrorMessage('Clé API Web Sentinel manquante. Configurez-la via la commande "Web Sentinel: Configure API Credentials".');
			return;
		}

		const domain = await vscode.window.showInputBox({
			title: 'Domain à scanner',
			prompt: 'example.com',
			validateInput: (value) => (!value ? 'Le domaine est obligatoire.' : undefined)
		});

		if (!domain) {
			return;
		}

		this.statusBar.update('$(sync~spin) Web Sentinel: scan domaine...');

		const baseUrl = ensureTrailingSlashRemoved(config.apiUrl);
		const domainEndpoints = ['/scan-domain', '/scan'];

		for (const suffix of domainEndpoints) {
			try {
				const endpoint = normaliseEndpoint(baseUrl, suffix);
				const response = await axios.post<DomainScanResponse>(endpoint, {
					api_key: config.apiKey,
					domain,
					options: {
						timeout: 5,
						allow_invasive: false,
						format: 'json'
					}
				}, {
					headers: {
						'Content-Type': 'application/json',
						'User-Agent': 'Web-Sentinel-VSCode/0.1.0'
					},
					timeout: 60000
				});

				const findings = pickFindings(response.data);
				this.output.appendLine(`[${new Date().toISOString()}] Scan domaine ${domain} -> ${findings.length} trouvaille(s)`);
				this.statusBar.update(`$(shield) ${findings.length} issues`, `${findings.length} vulnérabilité(s) sur ${domain}`);

				if (findings.length) {
					const items = findings.slice(0, 5).map((finding) => `[${finding.severity.toUpperCase()}] ${finding.title}`);
					void vscode.window.showWarningMessage(`Web Sentinel: ${findings.length} vulnérabilité(s) détectée(s) sur ${domain}.`, ...items);
				} else {
					void vscode.window.showInformationMessage(`Web Sentinel: aucune vulnérabilité détectée pour ${domain}.`);
				}
				return;
			} catch (error) {
				if (axios.isAxiosError(error) && error.response?.status === 404) {
					continue;
				}
				const message = axios.isAxiosError(error) ? describeAxiosError(error) : (error as Error).message;
				this.output.appendLine(`[${new Date().toISOString()}] Échec du scan domaine ${domain}: ${message}`);
				this.statusBar.update('$(shield-x) Web Sentinel', message);
				void vscode.window.showErrorMessage(`Web Sentinel: échec du scan de ${domain}. ${message}`);
				return;
			}
		}

		this.statusBar.update('$(shield-alert) Web Sentinel', 'Endpoint domain introuvable');
		void vscode.window.showErrorMessage('Web Sentinel: aucun endpoint de scan domaine disponible sur cette API.');
	}

	async configureCredentials(): Promise<void> {
		const config = getConfiguration();

		const apiUrl = await vscode.window.showInputBox({
			title: 'URL API Web Sentinel',
			prompt: 'https://api.web-sentinel.taaazzz-prog.fr/api/v1',
			value: config.apiUrl
		});
		if (!apiUrl) {
			return;
		}

		const apiKey = await vscode.window.showInputBox({
			title: 'Clé API Web Sentinel',
			prompt: 'ws_xxx...',
			value: config.apiKey ?? '',
			password: true
		});
		if (!apiKey) {
			return;
		}

		const licenseEmail = await vscode.window.showInputBox({
			title: 'Email licence Web Sentinel (optionnel)',
			value: config.licenseEmail ?? ''
		});

		const licenseTier = await vscode.window.showQuickPick(['free', 'pro', 'enterprise', 'sysop'], {
			title: 'Tier licence attendu',
			placeHolder: 'Sélectionnez le tier correspondant à votre abonnement',
			canPickMany: false
		});

		const settings = vscode.workspace.getConfiguration('webSentinel');
		await settings.update('apiUrl', apiUrl, vscode.ConfigurationTarget.Global);
		await settings.update('apiKey', apiKey, vscode.ConfigurationTarget.Global);
		await settings.update('licenseEmail', licenseEmail ?? '', vscode.ConfigurationTarget.Global);
		if (licenseTier) {
			await settings.update('licenseTier', licenseTier, vscode.ConfigurationTarget.Global);
		}

		void vscode.window.showInformationMessage('Paramètres Web Sentinel mis à jour.');
		this.statusBar.update('$(shield-check) Web Sentinel', 'Configuration API enregistrée');
	}

	async verifyLicense(): Promise<void> {
		const config = getConfiguration();
		if (!config.licenseEmail) {
			void vscode.window.showErrorMessage('Aucune adresse email de licence configurée.');
			return;
		}

		const baseUrl = ensureTrailingSlashRemoved(config.apiUrl);
		const endpoint = normaliseEndpoint(baseUrl, '/license/verify');

		this.statusBar.update('$(sync~spin) Web Sentinel: vérification licence...');

		try {
			const response = await axios.post<LicenseVerificationResponse>(endpoint, {
				email: config.licenseEmail,
				tier: config.licenseTier
			}, {
				headers: {
					'Content-Type': 'application/json',
					'User-Agent': 'Web-Sentinel-VSCode/0.1.0'
				},
				timeout: 20000
			});

			if (!response.data.valid) {
				const reason = response.data.reason ?? 'Licence invalide.';
				this.statusBar.update('$(shield-alert) Licence Web Sentinel', reason);
				void vscode.window.showErrorMessage(`Licence Web Sentinel invalide: ${reason}`);
				return;
			}

			const tier = response.data.tier ?? config.licenseTier;
			const limits = response.data.limits ? `Limites journalières: ${response.data.limits.daily ?? 'N/A'}, mensuelles: ${response.data.limits.monthly ?? 'N/A'}` : undefined;
			this.statusBar.update('$(shield-check) Licence Web Sentinel', `Licence valide (${tier})`);
			void vscode.window.showInformationMessage(`Licence Web Sentinel valide (${tier}). ${limits ?? ''}`);
		} catch (error) {
			const message = axios.isAxiosError(error) ? describeAxiosError(error) : (error as Error).message;
			this.statusBar.update('$(shield-alert) Licence Web Sentinel', message);
			void vscode.window.showErrorMessage(`Impossible de vérifier la licence Web Sentinel. ${message}`);
		}
	}

	registerAutoScan(context: vscode.ExtensionContext): void {
		context.subscriptions.push(
			vscode.workspace.onDidSaveTextDocument(async (document) => {
				const config = getConfiguration();
				if (!config.autoScanOnSave) {
					return;
				}
				if (!SUPPORTED_LANGUAGES.includes(document.languageId)) {
					return;
				}
				await this.scanDocument(document);
			})
		);
	}
}

export function activate(context: vscode.ExtensionContext): void {
	const extension = new WebSentinelExtension();
	context.subscriptions.push(extension);

	context.subscriptions.push(
		vscode.commands.registerCommand('web-sentinel.scanFile', () => extension.scanActiveFile()),
		vscode.commands.registerCommand('web-sentinel.scanWorkspace', () => extension.scanWorkspace()),
		vscode.commands.registerCommand('web-sentinel.scanDomain', () => extension.scanDomain()),
		vscode.commands.registerCommand('web-sentinel.configureCredentials', () => extension.configureCredentials()),
		vscode.commands.registerCommand('web-sentinel.verifyLicense', () => extension.verifyLicense())
	);

	extension.registerAutoScan(context);
}

export function deactivate(): void {
	// Nothing to clean up manually; disposal handled by subscriptions.
}
