<?php

declare(strict_types=1);
/**
 * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

namespace OCA\Talk\Service;

use OCA\Talk\Chat\CommentsManager;
use OCA\Talk\Chat\MessageParser;
use OCA\Talk\Model\Message;
use OCA\Talk\Model\ScheduledMessage;
use OCA\Talk\Model\ScheduledMessageMapper;
use OCA\Talk\Model\Thread;
use OCA\Talk\Participant;
use OCA\Talk\ResponseDefinitions;
use OCA\Talk\Room;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Comments\IComment;
use OCP\Comments\MessageTooLongException;
use OCP\DB\Exception;
use OCP\IL10N;
use Psr\Log\LoggerInterface;

/**
 * @psalm-import-type TalkScheduledMessage from ResponseDefinitions
 */
class ScheduledMessageService {
	public function __construct(
		private readonly ScheduledMessageMapper $scheduledMessageMapper,
		private readonly ThreadService $threadService,
		private readonly ITimeFactory $timeFactory,
		private readonly IL10N $l,
		private readonly LoggerInterface $logger,
		private readonly CommentsManager $commentsManager,
		private readonly MessageParser $messageParser,
	) {
	}

	/**
	 * @throws MessageTooLongException When the message is too long (~32k characters)
	 */
	public function scheduleMessage(
		Room $chat,
		Participant $participant,
		string $message,
		string $messageType,
		?IComment $parent,
		int $threadId,
		\DateTime $sendAt,
		array $metadata = [],
	): ScheduledMessage {
		$scheduledMessage = new ScheduledMessage();
		$scheduledMessage->setRoomId($chat->getId());
		$scheduledMessage->setActorId($participant->getAttendee()->getActorId());
		$scheduledMessage->setActorType($participant->getAttendee()->getActorType());
		$scheduledMessage->setSendAt($sendAt);
		$scheduledMessage->setMessage($message);
		$scheduledMessage->setMessageType($messageType);
		if ($parent instanceof IComment) {
			$scheduledMessage->setParentId((int)$parent->getId());
		}
		$scheduledMessage->setThreadId($threadId);
		$scheduledMessage->setMetaData($metadata);

		$this->scheduledMessageMapper->insert($scheduledMessage);

		return $scheduledMessage;
	}

	public function deleteMessage(Room $chat, string $id, string $actorType, string $actorId): int {
		return $this->scheduledMessageMapper->deleteById(
			$chat,
			$id,
			$actorType,
			$actorId,
		);
	}

	/**
	 * @throws DoesNotExistException
	 * @throws MessageTooLongException
	 * @throws \InvalidArgumentException
	 */
	public function editMessage(
		Room $chat,
		string $id,
		Participant $participant,
		string $text,
		bool $isSilent,
		?\DateTime $sendAt = null,
		string $threadTitle = '',
	): ScheduledMessage {
		$message = $this->scheduledMessageMapper->findById(
			$chat,
			$id,
			$participant->getAttendee()->getActorType(),
			$participant->getAttendee()->getActorId()
		);

		$metaData = $message->getDecodedMetaData();
		if ($metaData[ScheduledMessage::METADATA_THREAD_ID] !== Thread::THREAD_CREATE && $threadTitle !== '') {
			throw new \InvalidArgumentException('thread-title');
		}

		if ($metaData[ScheduledMessage::METADATA_THREAD_ID] === Thread::THREAD_CREATE && $threadTitle !== '') {
			$metaData[ScheduledMessage::METADATA_THREAD_TITLE] = $threadTitle;
		}

		$metaData[ScheduledMessage::METADATA_LAST_EDITED_TIME] = $this->timeFactory->getTime();
		$metaData[ScheduledMessage::METADATA_SILENT] = $isSilent;
		$message->setMetaData($metaData);
		$message->setMessage($text);
		$message->setSendAt($sendAt);
		$this->scheduledMessageMapper->update($message);

		return $message;
	}

	public function deleteByActor(string $actorType, string $actorId): void {
		$this->scheduledMessageMapper->deleteByActor($actorType, $actorId);
	}

	/**
	 * @return list<TalkScheduledMessage>
	 */
	public function getMessages(Room $chat, Participant $participant, string $format): array {
		$result = $this->scheduledMessageMapper->findByRoomAndActor(
			$chat,
			$participant->getAttendee()->getActorType(),
			$participant->getAttendee()->getActorId()
		);

		if (empty($result)) {
			return [];
		}

		$commentIds = array_filter(array_map(static fn (array $result) => $result['parent_id'], $result));
		try {
			$comments = $this->commentsManager->getCommentsById($commentIds);
		} catch (Exception) {
			$comments = [];
		}

		$messages = [];
		foreach ($result as $row) {
			$parent = $thread = null;
			$entity = [];
			foreach ($row as $field => $value) {
				if (str_starts_with((string)$field, 'th_')) {
					$thread[substr((string)$field, 3)] = $value;
					continue;
				}
				$entity[$field] = $value;
			}

			$scheduleMessage = ScheduledMessage::fromRow($entity);
			if ($entity['parent_id'] !== null && isset($comments[$entity['parent_id']])) {
				$parent = $this->messageParser->createMessage($chat, $participant, $comments[$entity['parent_id']], $this->l);
				$this->messageParser->parseMessage($parent);
			}
			if (in_array($thread['id'], [null, Thread::THREAD_NONE, Thread::THREAD_CREATE], true)) {
				$thread = null;
			} else {
				$thread = Thread::fromRow($thread);
			}
			$messages[] = $this->parseScheduledMessage($format, $scheduleMessage, $parent, $thread);
		}

		return $messages;
	}

	public function parseScheduledMessage(string $format, ScheduledMessage $message, ?Message $parentMessage, ?Thread $thread = null): array {
		if ($thread === null
			&& $message->getThreadId() !== Thread::THREAD_NONE
			&& $message->getThreadId() !== Thread::THREAD_CREATE
		) {
			try {
				$thread = $this->threadService->findByThreadId(
					$message->getRoomId(),
					$message->getThreadId(),
				);
			} catch (DoesNotExistException $e) {
				$this->logger->warning('Could not find thread ' . (string)$message->getThreadId() . ' for scheduled message', ['exception' => $e]);
				$thread = null;
			}
		}
		return $message->toArray($format, $parentMessage, $thread ?? null);
	}

	public function getScheduledMessageCount(Room $chat, Participant $participant): int {
		return $this->scheduledMessageMapper->getCountByActorAndRoom(
			$chat,
			$participant->getAttendee()->getActorType(),
			$participant->getAttendee()->getActorId(),
		);
	}

	/**
	 * @return ScheduledMessage[]
	 */
	public function getDue(\DateTimeInterface $getDateTime): array {
		return $this->scheduledMessageMapper->getMessagesDue($getDateTime);
	}

	public function markAsFailed(ScheduledMessage $message): ScheduledMessage {
		$metaData = $message->getDecodedMetaData();
		if (!isset($metaData[ScheduledMessage::METADATA_SEND_AT])) {
			$metaData[ScheduledMessage::METADATA_SEND_AT] = $message->getSendAt()?->getTimestamp();
			$message->setMetaData($metaData);
		}
		$message->setSendAt(null);
		$this->scheduledMessageMapper->update($message);

		return $message;
	}

	public function deleteMessagesByRoom(Room $room): int {
		return $this->scheduledMessageMapper->deleteMessagesByRoom($room);
	}
}
