<?php

declare(strict_types=1);

namespace App\Modules\Administration\Services;

use PDO;
use RuntimeException;
use System\Contracts\DatabaseInterface;

final class InstallService
{
    private PDO $pdo;

    public function __construct(
        DatabaseInterface $database
    ) {
        $this->pdo = $database->getConnection();
    }

    public function installed(): bool
    {
        $statement = $this->pdo->query(
            'SELECT COUNT(*) FROM users'
        );

        return (int) $statement->fetchColumn() > 0;
    }

    public function createAdministrator(
        string $fullName,
        string $username,
        string $email,
        string $password
    ): void {
        if ($this->installed()) {
            throw new RuntimeException(
                'System installation has already been completed.'
            );
        }

        if (
            $fullName === ''
            || $username === ''
            || $email === ''
            || $password === ''
        ) {
            throw new RuntimeException(
                'All fields are required.'
            );
        }

        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new RuntimeException(
                'Invalid email address.'
            );
        }

        if (strlen($password) < 8) {
            throw new RuntimeException(
                'Password must contain at least 8 characters.'
            );
        }

        $this->pdo->beginTransaction();

        try {
            $roleStatement = $this->pdo->prepare(
                'SELECT id
                 FROM roles
                 WHERE role_name = ?
                 LIMIT 1'
            );

            $roleStatement->execute([
                'Administrator'
            ]);

            $roleId = $roleStatement->fetchColumn();

            if (!$roleId) {
                $insertRole = $this->pdo->prepare(
                    'INSERT INTO roles
                    (
                        role_name,
                        description,
                        active,
                        created_at,
                        updated_at
                    )
                    VALUES
                    (
                        ?,
                        ?,
                        1,
                        NOW(),
                        NOW()
                    )'
                );

                $insertRole->execute([
                    'Administrator',
                    'System Administrator'
                ]);

                $roleId = (int) $this->pdo->lastInsertId();
            }

            $userStatement = $this->pdo->prepare(
                'INSERT INTO users
                (
                    username,
                    email,
                    password,
                    full_name,
                    failed_attempts,
                    active,
                    created_at,
                    updated_at
                )
                VALUES
                (
                    ?,
                    ?,
                    ?,
                    ?,
                    0,
                    1,
                    NOW(),
                    NOW()
                )'
            );

            $userStatement->execute([
                $username,
                $email,
                password_hash(
                    $password,
                    PASSWORD_DEFAULT
                ),
                $fullName
            ]);

            $userId = (int) $this->pdo->lastInsertId();

            $assignmentStatement = $this->pdo->prepare(
                'INSERT INTO user_roles
                (
                    user_id,
                    role_id,
                    created_at,
                    updated_at
                )
                VALUES
                (
                    ?,
                    ?,
                    NOW(),
                    NOW()
                )'
            );

            $assignmentStatement->execute([
                $userId,
                $roleId
            ]);

            $this->pdo->commit();

        } catch (\Throwable $exception) {

            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }

            throw $exception;
        }
    }
}