<?php

namespace GriffinForms\Frontend\Security\Guards;

class SubmissionEditAccessGuard implements GuardInterface
{
    public function getKey(): string
    {
        return 'submission_edit_access';
    }

    public function getPriority(): int
    {
        return 30;
    }

    public function check(int $form_id, array $context = []): array
    {
        if ($form_id <= 0 || empty($context['is_edit'])) {
            return ['blocked' => false];
        }

        $form = new \GriffinForms\Items\Form();
        if (!$form->loadItem($form_id)) {
            return [
                'blocked' => true,
                'message' => __('This submission cannot be edited right now.', 'griffinforms-form-builder'),
            ];
        }

        $policy = $form->getProp('submission_edit');
        if (!is_array($policy) || empty($policy['toggle'])) {
            return [
                'blocked' => true,
                'message' => __('Submission editing is not enabled for this form.', 'griffinforms-form-builder'),
            ];
        }

        $submission_context = isset($context['submission_context']) && is_array($context['submission_context'])
            ? $context['submission_context']
            : [];
        $submission_id = isset($context['submission_id']) ? absint($context['submission_id']) : 0;
        if ($submission_id <= 0 && !empty($submission_context['submission_meta']['id'])) {
            $submission_id = absint($submission_context['submission_meta']['id']);
        }

        if ($submission_id <= 0) {
            return [
                'blocked' => true,
                'message' => __('This submission cannot be edited right now.', 'griffinforms-form-builder'),
            ];
        }

        $current_count = $this->getCurrentEditCount($submission_context, $submission_id);
        $max_edits = isset($policy['max_edits']) ? absint($policy['max_edits']) : 0;
        if ($max_edits > 0 && $current_count >= $max_edits) {
            return [
                'blocked' => true,
                'message' => __('This submission has reached its edit limit.', 'griffinforms-form-builder'),
            ];
        }

        $mode_checks = [];
        if (!empty($policy['original_submitters'])) {
            $mode_checks[] = $this->isOriginalSubmitter($submission_context);
        }
        if (!empty($policy['email_owners'])) {
            $mode_checks[] = $this->isEmailOwner($submission_context, $policy);
        }
        if (!empty($policy['role_access'])) {
            $mode_checks[] = $this->hasAllowedRole($policy);
        }
        if (!empty($policy['user_access'])) {
            $mode_checks[] = $this->isSelectedUser($policy);
        }

        if (empty($mode_checks) || !in_array(true, $mode_checks, true)) {
            return [
                'blocked' => true,
                'message' => __('You are not allowed to edit this submission.', 'griffinforms-form-builder'),
            ];
        }

        return ['blocked' => false];
    }

    private function getCurrentEditCount(array $submission_context, int $submission_id): int
    {
        if (!empty($submission_context['submission_meta']['submission_edit_count'])) {
            return absint($submission_context['submission_meta']['submission_edit_count']);
        }

        if ($submission_id <= 0) {
            return 0;
        }

        global $wpdb;
        $table = \GriffinForms\Config::getInstance()->getTable('submission');
        $count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT submission_edit_count FROM %i WHERE id = %d",
                $table,
                $submission_id
            )
        );

        return $count === null ? 0 : absint($count);
    }

    private function isOriginalSubmitter(array $submission_context): bool
    {
        if (!is_user_logged_in()) {
            return false;
        }

        $current_user_id = get_current_user_id();
        $submission_author = isset($submission_context['submission_meta']['author'])
            ? absint($submission_context['submission_meta']['author'])
            : 0;

        return $current_user_id > 0 && $submission_author > 0 && $current_user_id === $submission_author;
    }

    private function isEmailOwner(array $submission_context, array $policy): bool
    {
        if (!is_user_logged_in()) {
            return false;
        }

        $current_user = wp_get_current_user();
        $current_email = isset($current_user->user_email) ? strtolower(trim((string) $current_user->user_email)) : '';
        if ($current_email === '') {
            return false;
        }

        $field_ids = isset($policy['email_fields']) && is_array($policy['email_fields'])
            ? array_values(array_filter(array_map('absint', $policy['email_fields'])))
            : [];
        if (empty($field_ids)) {
            return false;
        }

        $field_values = isset($submission_context['field_values']) && is_array($submission_context['field_values'])
            ? $submission_context['field_values']
            : [];

        foreach ($field_ids as $field_id) {
            if ($field_id <= 0 || !array_key_exists($field_id, $field_values)) {
                continue;
            }

            foreach ($this->collectEmails($field_values[$field_id]) as $email) {
                if ($email !== '' && strtolower($email) === $current_email) {
                    return true;
                }
            }
        }

        return false;
    }

    private function hasAllowedRole(array $policy): bool
    {
        if (!is_user_logged_in()) {
            return false;
        }

        $roles = isset($policy['roles']) && is_array($policy['roles'])
            ? array_values(array_filter(array_map('sanitize_key', $policy['roles'])))
            : [];
        if (empty($roles)) {
            return false;
        }

        $current_user = wp_get_current_user();
        $user_roles = is_array($current_user->roles) ? $current_user->roles : [];
        return !empty(array_intersect($roles, $user_roles));
    }

    private function isSelectedUser(array $policy): bool
    {
        if (!is_user_logged_in()) {
            return false;
        }

        $users = isset($policy['users']) && is_array($policy['users'])
            ? array_values(array_filter(array_map('absint', $policy['users'])))
            : [];
        if (empty($users)) {
            return false;
        }

        return in_array(get_current_user_id(), $users, true);
    }

    /**
     * Recursively collect email strings from stored field values.
     *
     * @param mixed $value
     * @return array<int,string>
     */
    private function collectEmails($value): array
    {
        $emails = [];

        if (is_string($value)) {
            if (is_email($value)) {
                $emails[] = sanitize_email($value);
                return $emails;
            }

            if (preg_match_all('/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i', $value, $matches)) {
                foreach ($matches[0] as $match) {
                    $emails[] = sanitize_email($match);
                }
            }

            return array_values(array_filter(array_unique($emails)));
        }

        if (is_array($value)) {
            foreach ($value as $item) {
                $emails = array_merge($emails, $this->collectEmails($item));
            }
        }

        return array_values(array_filter(array_unique($emails)));
    }
}
