<?php

/**
 * Vvveb
 *
 * Copyright (C) 2022  Ziadin Givan
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

namespace Vvveb\Plugins\AiAssistant\Controller;

use Vvveb\Controller\Base;

/**
 * Server side store for AI assistant chat sessions.
 *
 * The browser keeps a localStorage mirror as offline cache, but the database
 * is the source of truth: sessions survive cache clears, work across devices
 * and are not subject to the ~5MB localStorage quota (histories embed base64
 * attachments). Rows are scoped by site + admin user; sync keys off the
 * client-generated uuid so re-imports are idempotent.
 *
 * Protocol (POST json to /admin/index.php?module=plugins/ai-assistant/sessions&action=<name>):
 *   {csrf}                                             -> index() list metadata (no messages payload)
 *   {csrf, uuid}                                       -> get() one session with messages
 *   {csrf, uuid, title, messages[]}                    -> save() upsert by uuid
 *   {csrf, uuid | uuid[]}                              -> delete()
 *   {csrf, sessions: [{uuid, title, messages[]}]}      -> import() bulk upsert (one-time localStorage migration)
 */
class Sessions extends Base {
	protected string $modelName = 'Plugins\AiAssistant\Session';

	// keep localStorage-quota-sized payloads out of the db column too
	const MAX_MESSAGES_JSON = 6291456; // 6MB

	protected function jsonResponse($data): void {
		header('Content-Type: application/json');
		echo json_encode($data);
	}

	protected function error(string $message, int $status = 400) {
		http_response_code($status);
		$this->jsonResponse(['error' => ['message' => $message]]);
		die();
	}

	protected function input(): array {
		$input = json_decode(file_get_contents('php://input'), true);
		if (! is_array($input)) {
			$this->error('Invalid request body');
		}
		// same check as Base::checkCsrf but against the json payload
		$csrf = $this->session->get('csrf');
		if (! $csrf || ($input['csrf'] ?? '') !== $csrf) {
			$this->error('Invalid csrf!', 403);
		}

		return $input;
	}

	protected function scope(): array {
		return [
			'site_id'  => (int)($this->global['site_id'] ?? 0),
			'admin_id' => (int)($this->global['admin_id'] ?? 0),
		];
	}

	protected function cleanUuid($uuid): string {
		return preg_replace('/[^A-Za-z0-9._-]/', '', (string)$uuid);
	}

	protected function cleanMessages($messages): array {
		if (! is_array($messages)) {
			throw new \InvalidArgumentException('Invalid messages');
		}
		if (count($messages) > 2000) {
			throw new \InvalidArgumentException('Too many messages (max 2000)');
		}

		return array_values($messages);
	}

	protected function encodeMessages(array $messages): string {
		$json = json_encode($messages);
		if ($json === false || strlen($json) > self::MAX_MESSAGES_JSON) {
			throw new \InvalidArgumentException('Session too large (max 6MB of messages)');
		}

		return $json;
	}

	protected function row(array $input): array {
		$messages = $this->cleanMessages($input['messages'] ?? []);
		$now      = date('Y-m-d H:i:s');

		return [
			'uuid'          => $this->cleanUuid($input['uuid'] ?? ''),
			'title'         => mb_substr((string)($input['title'] ?? 'Untitled'), 0, 255),
			'messages'      => $this->encodeMessages($messages),
			'message_count' => count($messages),
			'updated_at'    => $now,
		] + $this->scope();
	}

	public function index() {
		$this->input();
		$scope = $this->scope();
		$result = model($this->modelName)->getAll($scope + ['start' => 0, 'limit' => 50]);
		$this->jsonResponse(['data' => $result['ai_session'] ?? [], 'count' => $result['count'] ?? 0]);
		exit;
	}

	public function get() {
		$input = $this->input();
		$uuid  = $this->cleanUuid($input['uuid'] ?? '');
		if ($uuid === '') {
			$this->error('Missing uuid');
		}
		$row = model($this->modelName)->get(['uuid' => $uuid] + $this->scope());
		if (! $row) {
			$this->error('Session not found', 404);
		}
		$row['messages'] = json_decode($row['messages'] ?? '[]', true) ?: [];
		$this->jsonResponse(['data' => $row]);
		exit;
	}

	protected function upsert(array $input) {
		$row   = $this->row($input);
		if ($row['uuid'] === '') {
			throw new \InvalidArgumentException('Missing uuid');
		}
		$model = model($this->modelName);
		$scope = $this->scope();
		$edit  = $row;
		unset($edit['uuid'], $edit['site_id'], $edit['admin_id'], $edit['created_at']);
		$affected = $model->edit(['session' => $edit, 'uuid' => $row['uuid']] + $scope);
		if (! $affected) {
			$edit['created_at'] = date('Y-m-d H:i:s');
			$model->add(['session' => $row + ['created_at' => $edit['created_at']]]);
		}

		return $row['uuid'];
	}

	public function save() {
		$input = $this->input();
		try {
			$uuid = $this->upsert($input);
		} catch (\InvalidArgumentException $e) {
			$this->error($e->getMessage());
		}
		$this->jsonResponse(['data' => ['uuid' => $uuid]]);
		exit;
	}

	public function delete() {
		$input = $this->input();
		$uuids = $input['uuid'] ?? ($input['uuids'] ?? null);
		$uuids = array_values(array_filter(array_map([$this, 'cleanUuid'], (array)$uuids)));
		if (! $uuids) {
			$this->error('Missing uuid');
		}
		$affected = model($this->modelName)->delete(['uuid' => $uuids] + $this->scope());
		$this->jsonResponse(['data' => ['deleted' => $affected]]);
		exit;
	}

	public function import() {
		$input    = $this->input();
		$sessions = $input['sessions'] ?? null;
		if (! is_array($sessions) || count($sessions) > 50) {
			$this->error('Invalid sessions (max 50)');
		}
		$imported = 0;
		foreach ($sessions as $session) {
			if (! is_array($session)) {
				continue;
			}
			try {
				$this->upsert($session);
				$imported++;
			} catch (\Exception $e) {
				// skip oversized/invalid sessions, keep importing the rest
				continue;
			}
		}
		$this->jsonResponse(['data' => ['imported' => $imported]]);
		exit;
	}
}
