<?php

namespace WPSN\Modules\Redirects;

use WPSN\Core\Logger;

defined('ABSPATH') || exit;

/**
 * Tracks the last known public permalink for posts/pages and creates
 * conservative exact 301 redirects when that URL changes.
 */
final class AutoPostRedirectService
{
    public const META_LAST_PUBLIC_PERMALINK = '_wpsn_last_public_permalink';

    private RedirectRepository $redirects;

    private RedirectsSettingsRepository $settings;

    private Logger $logger;

    public function __construct(
        RedirectRepository $redirects,
        RedirectsSettingsRepository $settings,
        Logger $logger
    ) {
        $this->redirects = $redirects;
        $this->settings = $settings;
        $this->logger = $logger;
    }

    /**
     * Fires after a post is inserted or updated, once terms/meta are saved.
     *
     * @param \WP_Post|null $postBefore
     */
    public function handlePostUpdate(int $postId, \WP_Post $post, bool $update, $postBefore = null): void
    {
        unset($update);

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

        if ($this->isRevisionOrAutosave($postId)) {
            return;
        }

        if (!$this->isSupportedPostType((string) ($post->post_type ?? ''))) {
            return;
        }

        $tracked = trim((string) get_post_meta($postId, self::META_LAST_PUBLIC_PERMALINK, true));
        $previousPublic = $this->publicPermalinkForPost($postBefore);
        if ($tracked === '' && $previousPublic !== '') {
            $tracked = $previousPublic;
        }

        $currentPublic = $this->publicPermalinkForPost($post);

        if ($currentPublic !== '') {
            if ($tracked !== '' && !empty($this->settings->getAll()['auto_redirect_post_url_changes'])) {
                $this->maybeCreateRedirect($postId, (string) ($post->post_type ?? ''), $tracked, $currentPublic);
            }

            update_post_meta($postId, self::META_LAST_PUBLIC_PERMALINK, esc_url_raw($currentPublic));
            return;
        }

        if ($tracked === '' && $previousPublic !== '') {
            update_post_meta($postId, self::META_LAST_PUBLIC_PERMALINK, esc_url_raw($previousPublic));
        }
    }

    private function maybeCreateRedirect(int $postId, string $postType, string $oldUrl, string $newUrl): void
    {
        $from = RedirectsUrl::normalizeFrom($oldUrl);
        $newPath = RedirectsUrl::normalizeFrom($newUrl);
        $to = RedirectsUrl::normalizeTo($newUrl);

        if ($from === '' || $from === '/' || $to === '') {
            return;
        }

        if (RedirectsUrl::urlsEqual($from, $newPath)) {
            return;
        }

        $existing = $this->redirects->findByFromAndType($from, 'exact');
        if (is_array($existing) && !empty($existing['id'])) {
            return;
        }

        $newId = $this->redirects->insert([
            'from_url' => $from,
            'to_url' => $to,
            'match_type' => 'exact',
            'status_code' => 301,
            'enabled' => true,
        ]);

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

        $payload = [
            'from_url' => $from,
            'to_url' => $to,
            'match_type' => 'exact',
            'status_code' => 301,
            'enabled' => true,
            'source' => 'auto_permalink_change',
            'post_id' => $postId,
            'post_type' => sanitize_key($postType),
            'old_url' => esc_url_raw($oldUrl),
            'new_url' => esc_url_raw($newUrl),
        ];

        $this->logger->info(sprintf('Auto redirect created for post %d: %s -> %s', $postId, $from, $to), $payload);

        /**
         * Fires when a redirect rule is created.
         */
        do_action('seo/redirect/created', $newId, $payload);
    }

    /**
     * @param mixed $post
     */
    private function publicPermalinkForPost($post): string
    {
        if (!$post instanceof \WP_Post) {
            return '';
        }

        $status = strtolower(trim((string) ($post->post_status ?? '')));
        if ($status !== 'publish') {
            return '';
        }

        $postType = (string) ($post->post_type ?? '');
        if (!$this->isSupportedPostType($postType)) {
            return '';
        }

        $url = get_permalink($post);
        $url = is_string($url) ? trim($url) : '';

        return $url !== '' ? esc_url_raw($url) : '';
    }

    private function isSupportedPostType(string $postType): bool
    {
        $postType = sanitize_key($postType);
        if ($postType === '' || $postType === 'attachment') {
            return false;
        }

        $types = get_post_types(['public' => true], 'names');
        if (!is_array($types)) {
            return in_array($postType, ['post', 'page'], true);
        }

        return in_array($postType, array_map('strval', $types), true);
    }

    private function isRevisionOrAutosave(int $postId): bool
    {
        if (function_exists('wp_is_post_revision') && wp_is_post_revision($postId)) {
            return true;
        }

        if (function_exists('wp_is_post_autosave') && wp_is_post_autosave($postId)) {
            return true;
        }

        return false;
    }
}
