<?php

declare(strict_types=1);

// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
// SPDX-License-Identifier: AGPL-3.0-or-later

namespace OCA\Forum\Controller;

use OCA\Forum\Attribute\RequirePermission;
use OCA\Forum\Db\CategoryPerm;
use OCA\Forum\Db\CategoryPermMapper;
use OCA\Forum\Db\Role;
use OCA\Forum\Db\RoleMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use Psr\Log\LoggerInterface;

class RoleController extends OCSController {
	public function __construct(
		string $appName,
		IRequest $request,
		private RoleMapper $roleMapper,
		private CategoryPermMapper $categoryPermMapper,
		private LoggerInterface $logger,
	) {
		parent::__construct($appName, $request);
	}

	/**
	 * Get all roles
	 *
	 * @param int<1, 100> $limit Maximum number of roles to return
	 * @param int<0, max> $offset Offset for pagination
	 * @return DataResponse<Http::STATUS_OK, list<array<string, mixed>>, array{}>
	 *
	 * 200: Roles returned
	 */
	#[NoAdminRequired]
	#[RequirePermission('canAccessAdminTools')]
	#[ApiRoute(verb: 'GET', url: '/api/roles')]
	public function index(int $limit = 100, int $offset = 0): DataResponse {
		try {
			$roles = array_slice($this->roleMapper->findAll(), $offset, $limit);
			return new DataResponse(array_map(fn ($role) => $role->jsonSerialize(), $roles));
		} catch (\Exception $e) {
			$this->logger->error('Error fetching roles: ' . $e->getMessage());
			return new DataResponse(['error' => 'Failed to fetch roles'], Http::STATUS_INTERNAL_SERVER_ERROR);
		}
	}

	/**
	 * Get a single role
	 *
	 * @param int $id Role ID
	 * @return DataResponse<Http::STATUS_OK, array<string, mixed>, array{}>
	 *
	 * 200: Role returned
	 */
	#[NoAdminRequired]
	#[RequirePermission('canAccessAdminTools')]
	#[ApiRoute(verb: 'GET', url: '/api/roles/{id}')]
	public function show(int $id): DataResponse {
		try {
			$role = $this->roleMapper->find($id);
			return new DataResponse($role->jsonSerialize());
		} catch (DoesNotExistException $e) {
			return new DataResponse(['error' => 'Role not found'], Http::STATUS_NOT_FOUND);
		} catch (\Exception $e) {
			$this->logger->error('Error fetching role: ' . $e->getMessage());
			return new DataResponse(['error' => 'Failed to fetch role'], Http::STATUS_INTERNAL_SERVER_ERROR);
		}
	}

	/**
	 * Create a new role
	 *
	 * @param string $name Role name
	 * @param string|null $description Role description
	 * @param string|null $colorLight Light mode color
	 * @param string|null $colorDark Dark mode color
	 * @param bool $canAccessAdminTools Can access admin tools
	 * @param bool $canEditRoles Can edit roles
	 * @param bool $canEditCategories Can edit categories
	 * @return DataResponse<Http::STATUS_CREATED, array<string, mixed>, array{}>
	 *
	 * 201: Role created
	 */
	#[NoAdminRequired]
	#[RequirePermission('canEditRoles')]
	#[ApiRoute(verb: 'POST', url: '/api/roles')]
	public function create(
		string $name,
		?string $description = null,
		?string $colorLight = null,
		?string $colorDark = null,
		bool $canAccessAdminTools = false,
		bool $canEditRoles = false,
		bool $canEditCategories = false,
	): DataResponse {
		try {
			$role = new \OCA\Forum\Db\Role();
			$role->setName($name);
			$role->setDescription($description);
			$role->setColorLight($colorLight);
			$role->setColorDark($colorDark);
			$role->setCanAccessAdminTools($canAccessAdminTools);
			$role->setCanEditRoles($canEditRoles);
			$role->setCanEditCategories($canEditCategories);
			$role->setCreatedAt(time());

			/** @var \OCA\Forum\Db\Role */
			$createdRole = $this->roleMapper->insert($role);
			return new DataResponse($createdRole->jsonSerialize(), Http::STATUS_CREATED);
		} catch (\Exception $e) {
			$this->logger->error('Error creating role: ' . $e->getMessage());
			return new DataResponse(['error' => 'Failed to create role'], Http::STATUS_INTERNAL_SERVER_ERROR);
		}
	}

	/**
	 * Update a role
	 *
	 * @param int $id Role ID
	 * @param string|null $name Role name
	 * @param string|null $description Role description
	 * @param string|null $colorLight Light mode color
	 * @param string|null $colorDark Dark mode color
	 * @param bool|null $canAccessAdminTools Can access admin tools
	 * @param bool|null $canEditRoles Can edit roles
	 * @param bool|null $canEditCategories Can edit categories
	 * @return DataResponse<Http::STATUS_OK, array<string, mixed>, array{}>
	 *
	 * 200: Role updated
	 */
	#[NoAdminRequired]
	#[RequirePermission('canEditRoles')]
	#[ApiRoute(verb: 'PUT', url: '/api/roles/{id}')]
	public function update(
		int $id,
		?string $name = null,
		?string $description = null,
		?string $colorLight = null,
		?string $colorDark = null,
		?bool $canAccessAdminTools = null,
		?bool $canEditRoles = null,
		?bool $canEditCategories = null,
	): DataResponse {
		try {
			$role = $this->roleMapper->find($id);

			if ($name !== null) {
				$role->setName($name);
			}
			if ($description !== null) {
				$role->setDescription($description);
			}
			if ($colorLight !== null) {
				$role->setColorLight($colorLight);
			}
			if ($colorDark !== null) {
				$role->setColorDark($colorDark);
			}

			// Admin role always has all permissions - cannot be changed
			if ($role->getRoleType() === Role::ROLE_TYPE_ADMIN) {
				$role->setCanAccessAdminTools(true);
				$role->setCanEditRoles(true);
				$role->setCanEditCategories(true);
			} elseif ($role->getRoleType() === Role::ROLE_TYPE_GUEST) {
				// Guest role never has admin permissions - cannot be changed
				$role->setCanAccessAdminTools(false);
				$role->setCanEditRoles(false);
				$role->setCanEditCategories(false);
			} else {
				if ($canAccessAdminTools !== null) {
					$role->setCanAccessAdminTools($canAccessAdminTools);
				}
				if ($canEditRoles !== null) {
					$role->setCanEditRoles($canEditRoles);
				}
				if ($canEditCategories !== null) {
					$role->setCanEditCategories($canEditCategories);
				}
			}

			/** @var \OCA\Forum\Db\Role */
			$updatedRole = $this->roleMapper->update($role);
			return new DataResponse($updatedRole->jsonSerialize());
		} catch (DoesNotExistException $e) {
			return new DataResponse(['error' => 'Role not found'], Http::STATUS_NOT_FOUND);
		} catch (\Exception $e) {
			$this->logger->error('Error updating role: ' . $e->getMessage());
			return new DataResponse(['error' => 'Failed to update role'], Http::STATUS_INTERNAL_SERVER_ERROR);
		}
	}

	/**
	 * Delete a role
	 *
	 * @param int $id Role ID
	 * @return DataResponse<Http::STATUS_OK, array{success: bool}, array{}>|DataResponse<Http::STATUS_FORBIDDEN, array{error: string}, array{}>
	 *
	 * 200: Role deleted
	 * 403: Cannot delete system roles
	 */
	#[NoAdminRequired]
	#[RequirePermission('canEditRoles')]
	#[ApiRoute(verb: 'DELETE', url: '/api/roles/{id}')]
	public function destroy(int $id): DataResponse {
		try {
			$role = $this->roleMapper->find($id);

			// Prevent deleting system roles (Admin, Moderator, User)
			if ($role->getIsSystemRole()) {
				return new DataResponse(['error' => 'System roles cannot be deleted'], Http::STATUS_FORBIDDEN);
			}

			// Delete associated permissions
			$this->categoryPermMapper->deleteByRoleId($id);

			$this->roleMapper->delete($role);
			return new DataResponse(['success' => true]);
		} catch (DoesNotExistException $e) {
			return new DataResponse(['error' => 'Role not found'], Http::STATUS_NOT_FOUND);
		} catch (\Exception $e) {
			$this->logger->error('Error deleting role: ' . $e->getMessage());
			return new DataResponse(['error' => 'Failed to delete role'], Http::STATUS_INTERNAL_SERVER_ERROR);
		}
	}

	/**
	 * Get permissions for a role
	 *
	 * @param int $id Role ID
	 * @param int<1, 100> $limit Maximum number of permissions to return
	 * @param int<0, max> $offset Offset for pagination
	 * @return DataResponse<Http::STATUS_OK, list<array<string, mixed>>, array{}>
	 *
	 * 200: Permissions returned
	 */
	#[NoAdminRequired]
	#[RequirePermission('canAccessAdminTools')]
	#[ApiRoute(verb: 'GET', url: '/api/roles/{id}/permissions')]
	public function getPermissions(int $id, int $limit = 100, int $offset = 0): DataResponse {
		try {
			$permissions = array_slice($this->categoryPermMapper->findByRoleId($id), $offset, $limit);
			return new DataResponse(array_map(fn ($perm) => $perm->jsonSerialize(), $permissions));
		} catch (\Exception $e) {
			$this->logger->error('Error fetching role permissions: ' . $e->getMessage());
			return new DataResponse(['error' => 'Failed to fetch permissions'], Http::STATUS_INTERNAL_SERVER_ERROR);
		}
	}

	/**
	 * Update permissions for a role
	 *
	 * @param int $id Role ID
	 * @param list<array{categoryId: int, canView: bool, canPost: bool, canReply: bool, canModerate: bool}> $permissions Permissions array
	 * @return DataResponse<Http::STATUS_OK, array{success: bool}, array{}>
	 *
	 * 200: Permissions updated
	 */
	#[NoAdminRequired]
	#[RequirePermission('canEditRoles')]
	#[ApiRoute(verb: 'POST', url: '/api/roles/{id}/permissions')]
	public function updatePermissions(int $id, array $permissions): DataResponse {
		try {
			// Verify role exists and get role type
			$role = $this->roleMapper->find($id);

			// Delete existing permissions for this role
			$this->categoryPermMapper->deleteByRoleId($id);

			// Insert new permissions
			foreach ($permissions as $perm) {
				$categoryPerm = new CategoryPerm();
				$categoryPerm->setCategoryId($perm['categoryId']);
				$categoryPerm->setTargetType(CategoryPerm::TARGET_TYPE_ROLE);
				$categoryPerm->setTargetId((string)$id);
				$categoryPerm->setCanView($perm['canView'] ?? false);
				$categoryPerm->setCanPost($perm['canPost'] ?? $perm['canView'] ?? false);
				$categoryPerm->setCanReply($perm['canReply'] ?? $perm['canPost'] ?? $perm['canView'] ?? false);
				// Guest and Default roles never have moderate permission
				$categoryPerm->setCanModerate($role->isModeratorRestricted() ? false : ($perm['canModerate'] ?? false));

				$this->categoryPermMapper->insert($categoryPerm);
			}

			return new DataResponse(['success' => true]);
		} catch (DoesNotExistException $e) {
			return new DataResponse(['error' => 'Role not found'], Http::STATUS_NOT_FOUND);
		} catch (\Exception $e) {
			$this->logger->error('Error updating role permissions: ' . $e->getMessage());
			return new DataResponse(['error' => 'Failed to update permissions'], Http::STATUS_INTERNAL_SERVER_ERROR);
		}
	}
}
