<?php
/**
 * Carteasy – Revocation (elektronischer Widerruf nach EU-RL 2026/2673)
 *
 * Zuständig für:
 *   - Erzeugen eines Widerrufs-Tokens bei Bestellabschluss (B2C only)
 *   - Validierung des Tokens auf der Landingpage
 *   - Verarbeitung der Widerrufserklärung (Status, Mails an Kunde + Shop)
 *
 * Silent-Modus ist möglich, siehe revocation_config.inc.php.
 *
 * @copyright  Wlanium / Thomas Bartelt
 * @since      2026-04-19
 */

include_once $_SERVER['DOCUMENT_ROOT'].'/core/config/revocation_config.inc.php';
include_once $_SERVER['DOCUMENT_ROOT'].'/core/revocationhelper.class.php';
include_once $_SERVER['DOCUMENT_ROOT'].'/core/mail.class.php';

class Revocation {

	private $base_object;
	private $db;
	private $error = '';

	public function __construct($base_object) {
		$this->base_object = $base_object;
		$this->db = $base_object->db;
	}

	public function get_error() {
		return $this->error;
	}

	// ============================================================
	// 1) Bei Bestellabschluss aufrufen
	// ============================================================

	/**
	 * Legt ein Revocation-Record für eine Bestellung an, sofern B2C.
	 * Versendet (wenn Flag an) die Info-Mail mit Widerrufs-Button.
	 *
	 * @param  int $order_id
	 * @return bool  true bei erfolgreicher Verarbeitung (auch "nicht zuständig" = true)
	 */
	public function create_for_order($order_id) {
		if (!REVOCATION_ENABLED) {
			return true; // Silent-Mode, nichts tun
		}

		$order_id = (int)$order_id;
		if ($order_id <= 0) {
			$this->error = 'invalid order_id';
			return false;
		}

		$order = $this->fetch_order($order_id);
		if (!$order) {
			$this->error = 'order not found';
			return false;
		}

		$group_id = RevocationHelper::resolve_group_id($this->db, $order->customer_id);
		if (!RevocationHelper::is_b2c($group_id)) {
			return true; // B2B oder unbekannt → kein Widerrufsbutton
		}

		if (empty($order->customer_email)) {
			$this->error = 'no customer_email on order';
			return false;
		}

		// Bereits vorhanden? (UK auf order_id → skippen)
		if ($this->exists_for_order($order_id)) {
			return true;
		}

		$token_plain = RevocationHelper::generate_token();
		$token_hash  = RevocationHelper::hash_token($token_plain);
		$expires_at  = RevocationHelper::calc_expires_at($order->order_date);

		$stmt = $this->db->prepare(
			"INSERT INTO revocations
				(order_id, customer_id, customer_email, customer_group_id,
				 token_hash, expires_at, status)
			 VALUES (?, ?, ?, ?, ?, ?, 'pending')"
		);
		if (!$stmt) {
			$this->error = 'prepare failed: '.$this->db->error;
			return false;
		}
		$customer_id = $order->customer_id ? (int)$order->customer_id : null;
		$stmt->bind_param(
			'iisiss',
			$order_id,
			$customer_id,
			$order->customer_email,
			$group_id,
			$token_hash,
			$expires_at
		);
		if (!$stmt->execute()) {
			$this->error = 'insert failed: '.$stmt->error;
			$stmt->close();
			return false;
		}
		$stmt->close();

		if (REVOCATION_INFO_MAIL_ENABLED) {
			$this->send_info_mail($order, $token_plain);
		}

		return true;
	}

	// ============================================================
	// 2) Landingpage – Token validieren
	// ============================================================

	/**
	 * Prüft Token auf Existenz, Gültigkeit, Status.
	 * @return object|false  Revocation-Row + Order-Row gemerged, oder false
	 */
	public function validate_token($token_plain) {
		if (!REVOCATION_ENABLED) {
			$this->error = 'feature_disabled';
			return false;
		}
		$token_plain = trim((string)$token_plain);
		if (strlen($token_plain) !== REVOCATION_TOKEN_BYTES * 2) {
			$this->error = 'invalid_token';
			return false;
		}

		$hash = RevocationHelper::hash_token($token_plain);
		$esc  = $this->db->real_escape_string($hash);

		$sql = "SELECT r.*,
					   o.order_number, o.order_date, o.order_total,
					   o.billing_firstname, o.billing_surname,
					   o.payment_method AS payment_method_id
				FROM revocations r
				JOIN orders o ON o.id = r.order_id
				WHERE r.token_hash = '$esc'
				LIMIT 1";
		$res = $this->db->query($sql);
		if (!$res || $res->num_rows === 0) {
			$this->error = 'invalid_token';
			return false;
		}
		$row = $res->fetch_object();

		if ($row->status !== 'pending') {
			$this->error = ($row->status === 'submitted' || $row->status === 'processed')
				? 'already_submitted' : 'not_eligible';
			return false;
		}

		if (strtotime($row->expires_at) < time()) {
			// Auto-Expire markieren
			$this->db->query("UPDATE revocations SET status='expired' WHERE id = ".(int)$row->id);
			$this->error = 'expired';
			return false;
		}

		return $row;
	}

	// ============================================================
	// 3) Landingpage – Widerruf einreichen
	// ============================================================

	/**
	 * Kunde hat auf der Landingpage den Widerruf abgeschickt.
	 * Validiert nochmal, setzt Status, versendet beide Mails.
	 */
	public function submit($token_plain, $reason_id, $reason_text, $ip, $user_agent) {
		$row = $this->validate_token($token_plain);
		if (!$row) {
			return false;
		}

		$reason_id   = ((int)$reason_id > 0) ? (int)$reason_id : null;
		$reason_text = trim((string)$reason_text);
		if (mb_strlen($reason_text) > 2000) {
			$reason_text = mb_substr($reason_text, 0, 2000);
		}
		$ip_anon = RevocationHelper::anonymize_ip($ip);
		$ua      = $user_agent ? mb_substr($user_agent, 0, 255) : null;

		$stmt = $this->db->prepare(
			"UPDATE revocations
				SET status = 'submitted',
					submitted_at = NOW(),
					reason_id = ?,
					reason_text = ?,
					ip_anonymized = ?,
					user_agent = ?
			  WHERE id = ? AND status = 'pending'"
		);
		if (!$stmt) {
			$this->error = 'prepare failed: '.$this->db->error;
			return false;
		}
		$id = (int)$row->id;
		$stmt->bind_param('isssi', $reason_id, $reason_text, $ip_anon, $ua, $id);
		$ok = $stmt->execute();
		$affected = $stmt->affected_rows;
		$stmt->close();

		if (!$ok || $affected < 1) {
			$this->error = 'update failed';
			return false;
		}

		// Mails senden (beide, auch wenn eine scheitert wir loggen)
		$fresh = $this->fetch_revocation($id);
		if ($fresh) {
			$this->send_owner_notification($fresh);
			$this->send_customer_confirmation($fresh);
		}

		return true;
	}

	// ============================================================
	// 4) Admin – als bearbeitet markieren
	// ============================================================

	public function mark_processed($revocation_id, $admin_note = '') {
		$revocation_id = (int)$revocation_id;
		$stmt = $this->db->prepare(
			"UPDATE revocations
				SET status = 'processed', processed_at = NOW(), admin_note = ?
			  WHERE id = ? AND status = 'submitted'"
		);
		$stmt->bind_param('si', $admin_note, $revocation_id);
		$ok = $stmt->execute();
		$stmt->close();
		return $ok;
	}

	// ============================================================
	// Mails
	// ============================================================

	private function send_info_mail($order, $token_plain) {
		$mailer = new mail_tools($this->base_object);
		$link   = RevocationHelper::build_link($token_plain);
		$name   = trim(($order->billing_firstname ?? '').' '.($order->billing_surname ?? ''));
		$order_number = $order->order_number ?: ('#'.$order->id);

		$subject = 'Ihre Widerrufsrechte zu Bestellung '.$order_number;

		$msg  = '<p>Sehr geehrte Kundin, sehr geehrter Kunde'.($name ? ' '.htmlspecialchars($name) : '').',</p>';
		$msg .= '<p>vielen Dank für Ihre Bestellung <strong>'.htmlspecialchars($order_number).'</strong>.</p>';
		$msg .= '<h3>Widerrufsrecht</h3>';
		$msg .= '<p>Sie haben das Recht, binnen 14 Tagen ohne Angabe von Gründen diesen Vertrag zu widerrufen. '
			  . 'Die Widerrufsfrist beträgt 14 Tage ab dem Tag, an dem Sie oder ein von Ihnen benannter Dritter, '
			  . 'der nicht der Beförderer ist, die Waren in Besitz genommen haben bzw. hat.</p>';
		$msg .= '<p>Um Ihr Widerrufsrecht elektronisch auszuüben, klicken Sie bitte auf den folgenden Button:</p>';
		$msg .= '<p style="margin:24px 0;text-align:center;">'
			  . '<a href="'.htmlspecialchars($link).'" '
			  . 'style="background:#c00;color:#fff;padding:14px 28px;text-decoration:none;'
			  . 'font-weight:bold;border-radius:4px;display:inline-block;">'
			  . htmlspecialchars(REVOCATION_BUTTON_LABEL)
			  . '</a></p>';
		$msg .= '<p style="font-size:0.85em;color:#666;">Falls der Button nicht funktioniert, kopieren Sie bitte '
			  . 'diesen Link in Ihren Browser:<br><code>'.htmlspecialchars($link).'</code></p>';
		$msg .= '<p>Der Link ist bis '.date('d.m.Y', strtotime(RevocationHelper::calc_expires_at($order->order_date))).' gültig.</p>';
		$msg .= '<p>Alternativ können Sie uns Ihren Widerruf auch formlos (E-Mail, Brief) mitteilen. '
			  . 'Unsere ausführliche Widerrufsbelehrung finden Sie auf unserer Website.</p>';
		$msg .= '<hr><p style="font-size:0.85em;color:#666;">'.REVOCATION_RETURN_COMPANY.'</p>';

		return $mailer->send_mail($subject, $msg, $order->customer_email);
	}

	private function send_owner_notification($revocation) {
		$mailer = new mail_tools($this->base_object);

		// Order + Payment-Method-Label + Reason-Label nachladen
		$order_id = (int)$revocation->order_id;
		$order = $this->fetch_order_with_payment($order_id);
		$reason_label = $this->fetch_reason_label($revocation->reason_id);

		$order_number = $order->order_number ?? ('#'.$order_id);
		$subject = 'Widerruf eingegangen – Bestellung '.$order_number;

		$msg  = '<h2>Widerruf eingegangen</h2>';
		$msg .= '<table cellpadding="4" cellspacing="0" border="0" style="font-family:Arial,sans-serif;">';
		$msg .= $this->mail_row('Bestellnummer',   $order_number);
		$msg .= $this->mail_row('Bestelldatum',    $order->order_date ?? '');
		$msg .= $this->mail_row('Kunde',           trim(($order->billing_firstname ?? '').' '.($order->billing_surname ?? '')));
		$msg .= $this->mail_row('E-Mail',          $revocation->customer_email);
		$msg .= $this->mail_row('Bestellsumme',    number_format((float)($order->order_total ?? 0), 2, ',', '.').' EUR');
		$msg .= $this->mail_row('Zahlungsart',     $order->payment_method_name ?? '–');
		$msg .= $this->mail_row('Widerrufsdatum',  $revocation->submitted_at);
		$msg .= $this->mail_row('Grund',           $reason_label ?: '(kein Grund angegeben)');
		if (!empty($revocation->reason_text)) {
			$msg .= $this->mail_row('Anmerkung', nl2br(htmlspecialchars($revocation->reason_text)));
		}
		$msg .= $this->mail_row('IP (anon.)',      $revocation->ip_anonymized ?? '–');
		$msg .= '</table>';
		$msg .= '<p style="margin-top:20px;"><strong>Bitte Rückzahlung über die Warenwirtschaft '
			  . 'veranlassen.</strong> Die Zahlung muss über dieselbe Zahlungsart wie die ursprüngliche '
			  . 'Bestellung erfolgen.</p>';
		$msg .= '<p>Admin: <a href="https://'.($_SERVER['HTTP_HOST'] ?? 'www.intelectra.de')
			  . '/index.php?admin_modul=admin_revocation_list">Widerrufsliste öffnen</a></p>';

		$ok = $mailer->send_mail($subject, $msg, REVOCATION_OWNER_EMAIL);

		if ($ok) {
			$this->db->query(
				"UPDATE revocations SET owner_notified_at = NOW() WHERE id = ".(int)$revocation->id
			);
		}
		return $ok;
	}

	private function send_customer_confirmation($revocation) {
		$mailer = new mail_tools($this->base_object);

		$order = $this->fetch_order((int)$revocation->order_id);
		$order_number = $order->order_number ?? ('#'.$revocation->order_id);
		$name = trim(($order->billing_firstname ?? '').' '.($order->billing_surname ?? ''));

		$subject = 'Bestätigung Ihres Widerrufs – Bestellung '.$order_number;

		$msg  = '<p>Sehr geehrte Kundin, sehr geehrter Kunde'.($name ? ' '.htmlspecialchars($name) : '').',</p>';
		$msg .= '<p>hiermit bestätigen wir den Eingang Ihrer Widerrufserklärung zu Bestellung '
			  . '<strong>'.htmlspecialchars($order_number).'</strong> am '
			  . date('d.m.Y H:i', strtotime($revocation->submitted_at)).' Uhr.</p>';
		$msg .= '<h3>Nächste Schritte</h3>';
		$msg .= '<p>Bitte senden Sie die Waren unverzüglich, spätestens binnen 14 Tagen, an:</p>';
		$msg .= '<p style="background:#f4f4f4;padding:12px;border-left:4px solid #c00;">'
			  . '<strong>'.REVOCATION_RETURN_COMPANY.'</strong><br>'
			  . REVOCATION_RETURN_STREET.'<br>'
			  . REVOCATION_RETURN_CITY.'</p>';
		$msg .= '<p>'.REVOCATION_RETURN_COSTS_NOTE.'</p>';
		$msg .= '<p>Die Rückzahlung erfolgt innerhalb von 14 Tagen nach Eingang Ihres Widerrufs über '
			  . 'dieselbe Zahlungsart, die Sie bei der ursprünglichen Bestellung verwendet haben – '
			  . 'es sei denn, mit Ihnen wurde ausdrücklich etwas anderes vereinbart.</p>';
		$msg .= '<p>Bei Rückfragen erreichen Sie uns jederzeit unter '
			  . '<a href="mailto:'.REVOCATION_OWNER_EMAIL.'">'.REVOCATION_OWNER_EMAIL.'</a>.</p>';
		$msg .= '<hr><p style="font-size:0.85em;color:#666;">'.REVOCATION_RETURN_COMPANY.'</p>';

		$ok = $mailer->send_mail($subject, $msg, $revocation->customer_email);

		if ($ok) {
			$this->db->query(
				"UPDATE revocations SET customer_confirmed_at = NOW() WHERE id = ".(int)$revocation->id
			);
		}
		return $ok;
	}

	private function mail_row($label, $value) {
		return '<tr><td style="color:#666;padding-right:12px;">'.htmlspecialchars($label)
			. ':</td><td><strong>'.($value ?: '–').'</strong></td></tr>';
	}

	// ============================================================
	// Queries
	// ============================================================

	private function fetch_order($order_id) {
		$order_id = (int)$order_id;
		$sql = "SELECT id, order_number, order_date, order_total, customer_id,
					   customer_email, billing_firstname, billing_surname,
					   payment_method
				FROM orders WHERE id = $order_id LIMIT 1";
		$res = $this->db->query($sql);
		if (!$res || $res->num_rows === 0) return false;
		return $res->fetch_object();
	}

	private function fetch_order_with_payment($order_id) {
		$order_id = (int)$order_id;
		$sql = "SELECT o.id, o.order_number, o.order_date, o.order_total,
					   o.billing_firstname, o.billing_surname,
					   pm.name AS payment_method_name
				FROM orders o
				LEFT JOIN payment_methods pm ON pm.id = o.payment_method
				WHERE o.id = $order_id LIMIT 1";
		$res = $this->db->query($sql);
		if (!$res || $res->num_rows === 0) return false;
		return $res->fetch_object();
	}

	private function fetch_revocation($id) {
		$id = (int)$id;
		$res = $this->db->query("SELECT * FROM revocations WHERE id = $id LIMIT 1");
		if (!$res || $res->num_rows === 0) return false;
		return $res->fetch_object();
	}

	private function fetch_reason_label($reason_id) {
		if (empty($reason_id)) return null;
		$reason_id = (int)$reason_id;
		$res = $this->db->query("SELECT label FROM revocation_reasons WHERE id = $reason_id LIMIT 1");
		if (!$res || $res->num_rows === 0) return null;
		return $res->fetch_object()->label;
	}

	private function exists_for_order($order_id) {
		$order_id = (int)$order_id;
		$res = $this->db->query("SELECT 1 FROM revocations WHERE order_id = $order_id LIMIT 1");
		return ($res && $res->num_rows > 0);
	}

	/**
	 * Öffentliche Liste für Admin-View.
	 */
	public function list_all($status = null, $limit = 200) {
		$limit = (int)$limit;
		$where = '';
		if ($status !== null && $status !== '') {
			$status = $this->db->real_escape_string($status);
			$where = "WHERE r.status = '$status'";
		}
		$sql = "SELECT r.id, r.order_id, r.customer_email, r.status,
					   r.submitted_at, r.processed_at, r.owner_notified_at,
					   o.order_number, o.order_date
				FROM revocations r
				LEFT JOIN orders o ON o.id = r.order_id
				$where
				ORDER BY r.created_at DESC
				LIMIT $limit";
		$res = $this->db->query($sql);
		$out = [];
		if ($res) {
			while ($obj = $res->fetch_object()) $out[] = $obj;
		}
		return $out;
	}

	public function fetch_reasons() {
		$res = $this->db->query(
			"SELECT id, label FROM revocation_reasons WHERE active = 1 ORDER BY sort, id"
		);
		$out = [];
		if ($res) {
			while ($o = $res->fetch_object()) $out[] = $o;
		}
		return $out;
	}

	// ============================================================
	// Anonymous-Pfad (§ 356a BGB – Button im Footer, ohne Token)
	// ============================================================

	/**
	 * Vorausfüllung des anonymen Formulars aus einem Token-Link.
	 * Liefert ['name','order_number','email'] oder false.
	 */
	public function prefill_from_token($token_plain) {
		if (!REVOCATION_ENABLED) {
			$this->error = 'feature_disabled';
			return false;
		}
		$token_plain = trim((string)$token_plain);
		if (strlen($token_plain) !== REVOCATION_TOKEN_BYTES * 2) {
			$this->error = 'invalid_token';
			return false;
		}
		$hash = RevocationHelper::hash_token($token_plain);
		$esc  = $this->db->real_escape_string($hash);
		$sql  = "SELECT r.customer_email,
						o.order_number,
						o.billing_firstname, o.billing_surname
				 FROM revocations r
				 JOIN orders o ON o.id = r.order_id
				 WHERE r.token_hash = '$esc'
				   AND r.status = 'pending'
				 LIMIT 1";
		$res = $this->db->query($sql);
		if (!$res || $res->num_rows === 0) {
			$this->error = 'invalid_token';
			return false;
		}
		$row = $res->fetch_object();
		return [
			'name'         => trim(($row->billing_firstname ?? '').' '.($row->billing_surname ?? '')),
			'order_number' => $row->order_number,
			'email'        => $row->customer_email,
		];
	}

	/**
	 * Widerruf ohne Token – Lookup anhand Bestellnr + E-Mail.
	 * Wenn noch kein revocation-Record existiert, wird er inline angelegt.
	 */
	public function submit_anonymous($name, $order_number, $email, $reason_id, $reason_text, $ip, $user_agent) {
		if (!REVOCATION_ENABLED) {
			$this->error = 'feature_disabled';
			return false;
		}

		$order_number = trim((string)$order_number);
		$email        = trim(strtolower((string)$email));

		if ($order_number === '') {
			$this->error = 'missing_order';
			return false;
		}
		if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
			$this->error = 'invalid_email';
			return false;
		}

		$order = $this->fetch_order_by_number_email($order_number, $email);
		if (!$order) {
			$this->error = 'order_not_found';
			return false;
		}

		$group_id = RevocationHelper::resolve_group_id($this->db, $order->customer_id);
		if (!RevocationHelper::is_b2c($group_id)) {
			$this->error = 'not_eligible';
			return false;
		}

		$expires_at = RevocationHelper::calc_expires_at($order->order_date);
		if (strtotime($expires_at) < time()) {
			$this->error = 'expired';
			return false;
		}

		// Existing Eintrag wiederverwenden, sonst inline anlegen
		$existing = $this->fetch_revocation_by_order((int)$order->id);
		if ($existing) {
			if ($existing->status !== 'pending') {
				$this->error = 'already_submitted';
				return false;
			}
			$revocation_id = (int)$existing->id;
		} else {
			$token_plain = RevocationHelper::generate_token();
			$token_hash  = RevocationHelper::hash_token($token_plain);
			$stmt = $this->db->prepare(
				"INSERT INTO revocations
					(order_id, customer_id, customer_email, customer_group_id,
					 token_hash, expires_at, status)
				 VALUES (?, ?, ?, ?, ?, ?, 'pending')"
			);
			if (!$stmt) {
				$this->error = 'prepare failed: '.$this->db->error;
				return false;
			}
			$customer_id = $order->customer_id ? (int)$order->customer_id : null;
			$order_id    = (int)$order->id;
			$stmt->bind_param(
				'iisiss',
				$order_id, $customer_id, $order->customer_email, $group_id,
				$token_hash, $expires_at
			);
			if (!$stmt->execute()) {
				$this->error = 'insert failed: '.$stmt->error;
				$stmt->close();
				return false;
			}
			$revocation_id = (int)$this->db->insert_id;
			$stmt->close();
		}

		$reason_id   = ((int)$reason_id > 0) ? (int)$reason_id : null;
		$reason_text = trim((string)$reason_text);
		if (mb_strlen($reason_text) > 2000) {
			$reason_text = mb_substr($reason_text, 0, 2000);
		}
		$ip_anon = RevocationHelper::anonymize_ip($ip);
		$ua      = $user_agent ? mb_substr($user_agent, 0, 255) : null;

		$stmt = $this->db->prepare(
			"UPDATE revocations
				SET status = 'submitted',
					submitted_at = NOW(),
					reason_id = ?,
					reason_text = ?,
					ip_anonymized = ?,
					user_agent = ?
			  WHERE id = ? AND status = 'pending'"
		);
		if (!$stmt) {
			$this->error = 'prepare failed: '.$this->db->error;
			return false;
		}
		$stmt->bind_param('isssi', $reason_id, $reason_text, $ip_anon, $ua, $revocation_id);
		$ok = $stmt->execute();
		$affected = $stmt->affected_rows;
		$stmt->close();
		if (!$ok || $affected < 1) {
			$this->error = 'update failed';
			return false;
		}

		$fresh = $this->fetch_revocation($revocation_id);
		if ($fresh) {
			$this->send_owner_notification($fresh);
			$this->send_customer_confirmation($fresh);
		}
		return true;
	}

	private function fetch_order_by_number_email($order_number, $email) {
		$on  = $this->db->real_escape_string($order_number);
		$em  = $this->db->real_escape_string($email);
		$sql = "SELECT id, order_number, order_date, order_total, customer_id,
					   customer_email, billing_firstname, billing_surname,
					   payment_method
				FROM orders
				WHERE order_number = '$on'
				  AND LOWER(customer_email) = '$em'
				LIMIT 1";
		$res = $this->db->query($sql);
		if (!$res || $res->num_rows === 0) return false;
		return $res->fetch_object();
	}

	private function fetch_revocation_by_order($order_id) {
		$order_id = (int)$order_id;
		$res = $this->db->query("SELECT * FROM revocations WHERE order_id = $order_id LIMIT 1");
		if (!$res || $res->num_rows === 0) return false;
		return $res->fetch_object();
	}
}
