<?php
/* Copyright (C) 2026 Alles Watt lauft
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 */

/**
 * Class AuditLog
 * Manages audit logging for KundenKarte module
 */
class AuditLog extends CommonObject
{
	public $element = 'auditlog';
	public $table_element = 'kundenkarte_audit_log';

	public $object_type;
	public $object_id;
	public $object_ref;
	public $fk_societe;
	public $fk_anlage;
	public $action;
	public $field_changed;
	public $old_value;
	public $new_value;
	public $fk_user;
	public $user_login;
	public $date_action;
	public $note;
	public $ip_address;

	// Loaded properties
	public $user_name;
	public $societe_name;

	// Action constants
	const ACTION_CREATE = 'create';
	const ACTION_UPDATE = 'update';
	const ACTION_DELETE = 'delete';
	const ACTION_MOVE = 'move';
	const ACTION_DUPLICATE = 'duplicate';
	const ACTION_STATUS_CHANGE = 'status';

	// Object type constants
	const TYPE_EQUIPMENT = 'equipment';
	const TYPE_CARRIER = 'carrier';
	const TYPE_PANEL = 'panel';
	const TYPE_ANLAGE = 'anlage';
	const TYPE_CONNECTION = 'connection';
	const TYPE_BUSBAR = 'busbar';
	const TYPE_EQUIPMENT_TYPE = 'equipment_type';
	const TYPE_BUSBAR_TYPE = 'busbar_type';

	/**
	 * Constructor
	 *
	 * @param DoliDB $db Database handler
	 */
	public function __construct($db)
	{
		$this->db = $db;
	}

	/**
	 * Log an action
	 *
	 * @param User   $user        User performing the action
	 * @param string $objectType  Type of object (equipment, carrier, panel, etc.)
	 * @param int    $objectId    ID of the object
	 * @param string $action      Action performed (create, update, delete, etc.)
	 * @param string $objectRef   Reference/label of the object (optional)
	 * @param string $fieldChanged Specific field changed (optional)
	 * @param mixed  $oldValue    Previous value (optional)
	 * @param mixed  $newValue    New value (optional)
	 * @param int    $socid       Customer ID (optional)
	 * @param int    $anlageId    Anlage ID (optional)
	 * @param string $note        Additional note (optional)
	 * @return int                Log entry ID or <0 on error
	 */
	public function log($user, $objectType, $objectId, $action, $objectRef = '', $fieldChanged = '', $oldValue = null, $newValue = null, $socid = 0, $anlageId = 0, $note = '')
	{
		global $conf;

		$now = dol_now();

		// Serialize complex values
		if (is_array($oldValue) || is_object($oldValue)) {
			$oldValue = json_encode($oldValue);
		}
		if (is_array($newValue) || is_object($newValue)) {
			$newValue = json_encode($newValue);
		}

		// Get IP address
		$ipAddress = '';
		if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
			$ipAddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
		} elseif (!empty($_SERVER['REMOTE_ADDR'])) {
			$ipAddress = $_SERVER['REMOTE_ADDR'];
		}

		$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
		$sql .= "entity, object_type, object_id, object_ref, fk_societe, fk_anlage,";
		$sql .= " action, field_changed, old_value, new_value,";
		$sql .= " fk_user, user_login, date_action, note, ip_address";
		$sql .= ") VALUES (";
		$sql .= ((int) $conf->entity);
		$sql .= ", '".$this->db->escape($objectType)."'";
		$sql .= ", ".((int) $objectId);
		$sql .= ", ".($objectRef ? "'".$this->db->escape($objectRef)."'" : "NULL");
		$sql .= ", ".($socid > 0 ? ((int) $socid) : "NULL");
		$sql .= ", ".($anlageId > 0 ? ((int) $anlageId) : "NULL");
		$sql .= ", '".$this->db->escape($action)."'";
		$sql .= ", ".($fieldChanged ? "'".$this->db->escape($fieldChanged)."'" : "NULL");
		$sql .= ", ".($oldValue !== null ? "'".$this->db->escape($oldValue)."'" : "NULL");
		$sql .= ", ".($newValue !== null ? "'".$this->db->escape($newValue)."'" : "NULL");
		$sql .= ", ".((int) $user->id);
		$sql .= ", '".$this->db->escape($user->login)."'";
		$sql .= ", '".$this->db->idate($now)."'";
		$sql .= ", ".($note ? "'".$this->db->escape($note)."'" : "NULL");
		$sql .= ", ".($ipAddress ? "'".$this->db->escape($ipAddress)."'" : "NULL");
		$sql .= ")";

		$resql = $this->db->query($sql);
		if (!$resql) {
			$this->error = $this->db->lasterror();
			return -1;
		}

		return $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
	}

	/**
	 * Log object creation
	 */
	public function logCreate($user, $objectType, $objectId, $objectRef = '', $socid = 0, $anlageId = 0, $data = null)
	{
		return $this->log($user, $objectType, $objectId, self::ACTION_CREATE, $objectRef, '', null, $data, $socid, $anlageId);
	}

	/**
	 * Log object update
	 */
	public function logUpdate($user, $objectType, $objectId, $objectRef = '', $fieldChanged = '', $oldValue = null, $newValue = null, $socid = 0, $anlageId = 0)
	{
		return $this->log($user, $objectType, $objectId, self::ACTION_UPDATE, $objectRef, $fieldChanged, $oldValue, $newValue, $socid, $anlageId);
	}

	/**
	 * Log object deletion
	 */
	public function logDelete($user, $objectType, $objectId, $objectRef = '', $socid = 0, $anlageId = 0, $data = null)
	{
		return $this->log($user, $objectType, $objectId, self::ACTION_DELETE, $objectRef, '', $data, null, $socid, $anlageId);
	}

	/**
	 * Log object move (position change)
	 */
	public function logMove($user, $objectType, $objectId, $objectRef = '', $oldPosition = null, $newPosition = null, $socid = 0, $anlageId = 0)
	{
		return $this->log($user, $objectType, $objectId, self::ACTION_MOVE, $objectRef, 'position', $oldPosition, $newPosition, $socid, $anlageId);
	}

	/**
	 * Log object duplication
	 */
	public function logDuplicate($user, $objectType, $objectId, $objectRef = '', $sourceId = 0, $socid = 0, $anlageId = 0)
	{
		return $this->log($user, $objectType, $objectId, self::ACTION_DUPLICATE, $objectRef, '', $sourceId, $objectId, $socid, $anlageId, 'Kopiert von ID '.$sourceId);
	}

	/**
	 * Fetch audit log entries for an object
	 *
	 * @param string $objectType Object type
	 * @param int    $objectId   Object ID
	 * @param int    $limit      Max entries (0 = no limit)
	 * @return array Array of AuditLog objects
	 */
	public function fetchByObject($objectType, $objectId, $limit = 50)
	{
		$results = array();

		$sql = "SELECT a.*, u.firstname, u.lastname, s.nom as societe_name";
		$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a";
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as u ON a.fk_user = u.rowid";
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON a.fk_societe = s.rowid";
		$sql .= " WHERE a.object_type = '".$this->db->escape($objectType)."'";
		$sql .= " AND a.object_id = ".((int) $objectId);
		$sql .= " ORDER BY a.date_action DESC";
		if ($limit > 0) {
			$sql .= " LIMIT ".((int) $limit);
		}

		$resql = $this->db->query($sql);
		if ($resql) {
			while ($obj = $this->db->fetch_object($resql)) {
				$log = new AuditLog($this->db);
				$log->id = $obj->rowid;
				$log->object_type = $obj->object_type;
				$log->object_id = $obj->object_id;
				$log->object_ref = $obj->object_ref;
				$log->fk_societe = $obj->fk_societe;
				$log->fk_anlage = $obj->fk_anlage;
				$log->action = $obj->action;
				$log->field_changed = $obj->field_changed;
				$log->old_value = $obj->old_value;
				$log->new_value = $obj->new_value;
				$log->fk_user = $obj->fk_user;
				$log->user_login = $obj->user_login;
				$log->date_action = $this->db->jdate($obj->date_action);
				$log->note = $obj->note;
				$log->ip_address = $obj->ip_address;
				$log->user_name = trim($obj->firstname.' '.$obj->lastname);
				$log->societe_name = $obj->societe_name;

				$results[] = $log;
			}
			$this->db->free($resql);
		}

		return $results;
	}

	/**
	 * Fetch audit log entries for an Anlage (installation)
	 *
	 * @param int  $anlageId  Anlage ID
	 * @param int  $limit     Max entries
	 * @return array Array of AuditLog objects
	 */
	public function fetchByAnlage($anlageId, $limit = 100)
	{
		$results = array();

		$sql = "SELECT a.*, u.firstname, u.lastname";
		$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a";
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as u ON a.fk_user = u.rowid";
		$sql .= " WHERE a.fk_anlage = ".((int) $anlageId);
		$sql .= " ORDER BY a.date_action DESC";
		if ($limit > 0) {
			$sql .= " LIMIT ".((int) $limit);
		}

		$resql = $this->db->query($sql);
		if ($resql) {
			while ($obj = $this->db->fetch_object($resql)) {
				$log = new AuditLog($this->db);
				$log->id = $obj->rowid;
				$log->object_type = $obj->object_type;
				$log->object_id = $obj->object_id;
				$log->object_ref = $obj->object_ref;
				$log->fk_societe = $obj->fk_societe;
				$log->fk_anlage = $obj->fk_anlage;
				$log->action = $obj->action;
				$log->field_changed = $obj->field_changed;
				$log->old_value = $obj->old_value;
				$log->new_value = $obj->new_value;
				$log->fk_user = $obj->fk_user;
				$log->user_login = $obj->user_login;
				$log->date_action = $this->db->jdate($obj->date_action);
				$log->note = $obj->note;
				$log->ip_address = $obj->ip_address;
				$log->user_name = trim($obj->firstname.' '.$obj->lastname);

				$results[] = $log;
			}
			$this->db->free($resql);
		}

		return $results;
	}

	/**
	 * Fetch audit log entries for a customer
	 *
	 * @param int  $socid   Societe ID
	 * @param int  $limit   Max entries
	 * @return array Array of AuditLog objects
	 */
	public function fetchBySociete($socid, $limit = 100)
	{
		$results = array();

		$sql = "SELECT a.*, u.firstname, u.lastname";
		$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a";
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as u ON a.fk_user = u.rowid";
		$sql .= " WHERE a.fk_societe = ".((int) $socid);
		$sql .= " ORDER BY a.date_action DESC";
		if ($limit > 0) {
			$sql .= " LIMIT ".((int) $limit);
		}

		$resql = $this->db->query($sql);
		if ($resql) {
			while ($obj = $this->db->fetch_object($resql)) {
				$log = new AuditLog($this->db);
				$log->id = $obj->rowid;
				$log->object_type = $obj->object_type;
				$log->object_id = $obj->object_id;
				$log->object_ref = $obj->object_ref;
				$log->fk_societe = $obj->fk_societe;
				$log->fk_anlage = $obj->fk_anlage;
				$log->action = $obj->action;
				$log->field_changed = $obj->field_changed;
				$log->old_value = $obj->old_value;
				$log->new_value = $obj->new_value;
				$log->fk_user = $obj->fk_user;
				$log->user_login = $obj->user_login;
				$log->date_action = $this->db->jdate($obj->date_action);
				$log->note = $obj->note;
				$log->ip_address = $obj->ip_address;
				$log->user_name = trim($obj->firstname.' '.$obj->lastname);

				$results[] = $log;
			}
			$this->db->free($resql);
		}

		return $results;
	}

	/**
	 * Get human-readable action label
	 *
	 * @return string Translated action label
	 */
	public function getActionLabel()
	{
		global $langs;

		switch ($this->action) {
			case self::ACTION_CREATE:
				return $langs->trans('AuditActionCreate');
			case self::ACTION_UPDATE:
				return $langs->trans('AuditActionUpdate');
			case self::ACTION_DELETE:
				return $langs->trans('AuditActionDelete');
			case self::ACTION_MOVE:
				return $langs->trans('AuditActionMove');
			case self::ACTION_DUPLICATE:
				return $langs->trans('AuditActionDuplicate');
			case self::ACTION_STATUS_CHANGE:
				return $langs->trans('AuditActionStatus');
			default:
				return $this->action;
		}
	}

	/**
	 * Get human-readable object type label
	 *
	 * @return string Translated object type label
	 */
	public function getObjectTypeLabel()
	{
		global $langs;

		switch ($this->object_type) {
			case self::TYPE_EQUIPMENT:
				return $langs->trans('Equipment');
			case self::TYPE_CARRIER:
				return $langs->trans('CarrierLabel');
			case self::TYPE_PANEL:
				return $langs->trans('PanelLabel');
			case self::TYPE_ANLAGE:
				return $langs->trans('Installation');
			case self::TYPE_CONNECTION:
				return $langs->trans('Connection');
			case self::TYPE_BUSBAR:
				return $langs->trans('Busbar');
			case self::TYPE_EQUIPMENT_TYPE:
				return $langs->trans('EquipmentType');
			case self::TYPE_BUSBAR_TYPE:
				return $langs->trans('BusbarType');
			default:
				return $this->object_type;
		}
	}

	/**
	 * Get action icon
	 *
	 * @return string FontAwesome icon class
	 */
	public function getActionIcon()
	{
		switch ($this->action) {
			case self::ACTION_CREATE:
				return 'fa-plus-circle';
			case self::ACTION_UPDATE:
				return 'fa-edit';
			case self::ACTION_DELETE:
				return 'fa-trash';
			case self::ACTION_MOVE:
				return 'fa-arrows';
			case self::ACTION_DUPLICATE:
				return 'fa-copy';
			case self::ACTION_STATUS_CHANGE:
				return 'fa-toggle-on';
			default:
				return 'fa-question';
		}
	}

	/**
	 * Get action color
	 *
	 * @return string CSS color
	 */
	public function getActionColor()
	{
		switch ($this->action) {
			case self::ACTION_CREATE:
				return '#27ae60';
			case self::ACTION_UPDATE:
				return '#3498db';
			case self::ACTION_DELETE:
				return '#e74c3c';
			case self::ACTION_MOVE:
				return '#9b59b6';
			case self::ACTION_DUPLICATE:
				return '#f39c12';
			case self::ACTION_STATUS_CHANGE:
				return '#1abc9c';
			default:
				return '#95a5a6';
		}
	}

	/**
	 * Clean old audit log entries
	 *
	 * @param int $daysToKeep Number of days to keep (default: 365)
	 * @return int Number of deleted entries or -1 on error
	 */
	public function cleanOldEntries($daysToKeep = 365)
	{
		$cutoffDate = dol_now() - ($daysToKeep * 24 * 60 * 60);

		$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element;
		$sql .= " WHERE date_action < '".$this->db->idate($cutoffDate)."'";

		$resql = $this->db->query($sql);
		if (!$resql) {
			$this->error = $this->db->lasterror();
			return -1;
		}

		return $this->db->affected_rows($resql);
	}
}
