// 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 { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { ConfigService } from './config.service';
import { WfActivity, WfComponent, WfInstance, WfJob, WfSnapshot, WfStartable } from '../models/workflow.model';

/**
 * Service del modulo Workflow (lato app).
 * Chiama ws_spring_workflow (configService.wfUrl). Il token JWT e l'header
 * `portale` sono aggiunti automaticamente dall'HttpInterceptor.
 *
 * Il backend filtra già per ruolo/utente quali istanze e attività sono
 * visibili ed eseguibili: l'app non applica gating per ruolo.
 */
@Injectable({
	providedIn: 'root',
})
export class WorkflowService {
	constructor(
		private http: HttpClient,
		private configService: ConfigService,
	) {}

	/**
	 * Elenco istanze workflow dell'azienda (storico).
	 * NB: `page` è 1-based — il backend calcola l'offset come (page-1)*limit.
	 */
	getInstanceList(cmp_id: number, cerca = '', orderby = '', page = 1, limit = 20): Observable<WfInstance[]> {
		const url = `${this.configService.wfUrl}instances/list/${cmp_id}?wfin_description=${cerca}&orderby=${orderby}&limit=${limit}&offset=${page}`;
		return this.http.get(url, { observe: 'response' }).pipe(map((r) => (r.body as WfInstance[]) || []));
	}

	/** Dettaglio di un'istanza con lo storico dei job (timeline). */
	getInstanceDetail(wfin_id: string): Observable<WfInstance> {
		const url = `${this.configService.wfUrl}instances/detail/${wfin_id}`;
		return this.http.get(url, { observe: 'response' }).pipe(map((r) => r.body as WfInstance));
	}

	/**
	 * Elenco job dell'azienda (già filtrato per ruolo dal backend).
	 * NB: `page` è 1-based — il backend calcola l'offset come (page-1)*limit.
	 */
	getJobList(cmp_id: number, cerca = '', orderby = '', page = 1, limit = 20): Observable<WfJob[]> {
		const url = `${this.configService.wfUrl}job/list/${cmp_id}?wfj_description=${cerca}&orderby=${orderby}&limit=${limit}&offset=${page}`;
		return this.http.get(url, { observe: 'response' }).pipe(map((r) => (r.body as WfJob[]) || []));
	}

	/** Attività che l'utente può INIZIARE (equivalente di /activity nel web). */
	getStartableActivities(cmp_id: number): Observable<WfStartable[]> {
		const url = `${this.configService.wfUrl}activity/list_by_user/${cmp_id}`;
		return this.http.get(url, { observe: 'response' }).pipe(map((r) => (r.body as WfStartable[]) || []));
	}

	/**
	 * Carica la prima attività di un workflow da avviare (modalità "start").
	 * Ritorna { wfa_jobparams } = JSON stringificato dell'attività.
	 */
	getStartActivity(wf_id: number, wf_state = 0): Observable<any> {
		const url = `${this.configService.wfUrl}activity/start/${wf_id}/${wf_state}`;
		return this.http.get(url, { observe: 'response' }).pipe(map((r) => r.body));
	}

	/** Carica un job per l'esecuzione (contiene wfj_data con l'attività). */
	getJob(wfj_id: string): Observable<WfJob> {
		const url = `${this.configService.wfUrl}job/by_id/${wfj_id}`;
		return this.http.get(url, { observe: 'response' }).pipe(map((r) => r.body as WfJob));
	}

	/** Componenti del workflow (per il render della form). */
	getComponents(wf_id: number): Observable<WfComponent[]> {
		const url = `${this.configService.wfUrl}activity/listcomponets/${wf_id}`;
		return this.http.get(url, { observe: 'response' }).pipe(map((r) => (r.body as WfComponent[]) || []));
	}

	/** Ricava il wf_id a partire dall'istanza. */
	getWfIdByInstance(wfin_id: string): Observable<any> {
		const url = `${this.configService.wfUrl}activity/wfid_by_instance/${wfin_id}`;
		return this.http.get(url, { observe: 'response' }).pipe(map((r) => r.body));
	}

	/** Persiste i dati del job (auto-save dei campi compilati). */
	updateJobData(wfj_id: string, jobData: any): Observable<any> {
		const url = `${this.configService.wfUrl}job/update_data/${wfj_id}`;
		return this.http.post(url, jobData, { observe: 'response' }).pipe(map((r) => r.body));
	}

	/**
	 * Esegue l'attività premendo un pulsante (button).
	 * Replica il payload del portale: { activity, button }.
	 * Ritorna { res: <next_wfj_id> } se c'è uno step successivo.
	 */
	execActivity(wf_id: number, wfj_id: string, activity: WfActivity, button: WfComponent): Observable<any> {
		const lang = this.configService.getLanguage() || 'it';
		const url = `${this.configService.wfUrl}activity/exec/${wf_id}?wfj_id=${wfj_id}&lang=${lang}`;
		return this.http.post(url, { activity, button }, { observe: 'response' }).pipe(map((r) => r.body));
	}

	/**
	 * Upload di un allegato (campo type 8) nella cartella temp documentale.
	 * Replica il flusso del portale: POST FormData 'fileUpload[]' →
	 * insert_pdf_file_in_folder_temp → { res } (repositoryName).
	 */
	uploadTempFile(file: File): Observable<string> {
		const fd = new FormData();
		fd.append('fileUpload[]', file);
		return this.http
			.post<{ res: string }>(`${this.configService.serviceUrl_Documentary}file/insert_pdf_file_in_folder_temp`, fd, { observe: 'response' })
			.pipe(map((r) => r.body?.res || ''));
	}

	/** URL pubblico di un allegato caricato (repositoryName servito sotto imageBaseURL). */
	uploadedFileUrl(repositoryName: string): string {
		if (!repositoryName) return '';
		return this.configService.imageBaseURL + '/' + String(repositoryName).replace(/\\/g, '/').replace(/^\/+/, '');
	}

	/**
	 * Risolve un file documentale (drfl_id) in un URL temporaneo scaricabile.
	 * Stesso flusso del resto dell'app: file/file_dwn_zip → imageBaseURL + /temp/<res>.
	 */
	getFileDownloadUrl(drfl_id: number): Observable<string> {
		const payload = { lst_folder: [], lst_file: [{ id: drfl_id, type: 'D' }] };
		return this.http
			.post<{ res: string }>(`${this.configService.serviceUrl_Documentary}file/file_dwn_zip`, payload, { observe: 'response' })
			.pipe(map((r) => (r.body?.res ? this.configService.imageBaseURL + '/temp/' + r.body.res : '')));
	}

	/**
	 * Estrae in modo sicuro l'attività e lo snapshot da un job.
	 *
	 * Contratto snapshot (popolato LATO WEB dentro wfj_data, l'app lo mostra read-only):
	 *   wfj_data = {
	 *     activity: {...},                 // l'attività eseguibile
	 *     snapshot?: {                     // dati dell'entità linkata, serializzati dal portale web
	 *       title?: string,
	 *       fields?: { label: string, value: string }[],
	 *       files?:  { drfl_id: number, filename?: string, mime?: string }[]
	 *     }
	 *   }
	 * Lo snapshot può stare anche dentro activity.snapshot. Se assente, l'app mostra
	 * solo l'etichetta del passo (fallback read-only) e gli eventuali file.
	 */
	parseJob(job: WfJob): { activity: WfActivity | null; snapshot: WfSnapshot | null; activityPre: WfActivity[] } {
		let activity: WfActivity | null = null;
		let snapshot: WfSnapshot | null = null;
		let activityPre: WfActivity[] = [];
		if (job?.wfj_data) {
			try {
				const data = JSON.parse(job.wfj_data);
				// wfj_data può essere l'attività diretta o un wrapper { activity, activity_pre, snapshot }
				activity = data?.activity ? data.activity : data;
				snapshot = data?.snapshot || activity?.snapshot || null;
				// activity_pre = step precedenti (es. l'attività di start non persistita come job)
				if (Array.isArray(data?.activity_pre)) activityPre = data.activity_pre;
				else if (data?.activity_pre) activityPre = [data.activity_pre];
			} catch {
				activity = null;
			}
		}
		return { activity, snapshot, activityPre };
	}
}
