<?php
/**
 * Redirect manager — exact-match and string-prefix redirects (longest
 * source_path wins for prefix matches; not regex), served on
 * `template_redirect`.
 *
 * @package Brainwerk SEO Suite
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class Seoforge_Redirects {

    public static function register( Seoforge_Loader $loader ) {
        if ( ! Seoforge_Settings::get( 'redirects_enabled', true ) ) {
            return;
        }
        $loader->add_action( 'template_redirect', __CLASS__, 'maybe_redirect', 1 );
    }

    public static function maybe_redirect() {
        if ( is_admin() ) {
            return;
        }
        $path = self::current_path();
        if ( '' === $path ) {
            return;
        }
        $rule = self::find_rule( $path );
        if ( ! $rule ) {
            return;
        }
        self::record_hit( (int) $rule['id'] );
        $status = (int) $rule['status_code'];
        if ( ! in_array( $status, array( 301, 302, 307, 308 ), true ) ) {
            $status = 301;
        }
        // wp_safe_redirect() only allows this site's own host by default, so an
        // external target_url would otherwise silently fall back to the site
        // home instead of actually redirecting. The target comes from the
        // redirect rule itself — an admin-configured, nonce/cap-gated CRUD
        // (add()/see 7.1.6) — not from unvalidated request input, so it's safe
        // to allow exactly that one host for this specific redirect.
        $target_host = wp_parse_url( $rule['target_url'], PHP_URL_HOST );
        if ( $target_host ) {
            add_filter( 'allowed_redirect_hosts', function ( $hosts ) use ( $target_host ) {
                $hosts[] = $target_host;
                return $hosts;
            } );
        }
        wp_safe_redirect( $rule['target_url'], $status );
        exit;
    }

    private static function current_path() {
        $uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
        $path = wp_parse_url( $uri, PHP_URL_PATH );
        return is_string( $path ) ? rtrim( $path, '/' ) : '';
    }

    /** Returns matching active rule (exact preferred, then prefix). */
    private static function find_rule( $path ) {
        global $wpdb;
        $table = Seoforge_Utils::table( 'redirects' );
        $blog  = Seoforge_Utils::current_blog_id();

        $exact = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM %i WHERE blog_id = %d AND is_active = 1 AND match_type = 'exact' AND source_path = %s LIMIT 1",
            $table,
            $blog,
            $path
        ), ARRAY_A );
        if ( $exact ) {
            return $exact;
        }
        // Prefix matching — longest-match wins.
        $candidates = $wpdb->get_results( $wpdb->prepare(
            "SELECT * FROM %i WHERE blog_id = %d AND is_active = 1 AND match_type = 'prefix'",
            $table,
            $blog
        ), ARRAY_A );
        $best = null;
        foreach ( (array) $candidates as $row ) {
            if ( 0 === strpos( $path, (string) $row['source_path'] ) ) {
                if ( ! $best || strlen( $row['source_path'] ) > strlen( $best['source_path'] ) ) {
                    $best = $row;
                }
            }
        }
        return $best;
    }

    private static function record_hit( $id ) {
        global $wpdb;
        $wpdb->query( $wpdb->prepare(
            "UPDATE %i SET hits = hits + 1, last_hit_at = %s WHERE id = %d",
            Seoforge_Utils::table( 'redirects' ),
            current_time( 'mysql', 1 ),
            (int) $id
        ) );
    }

    public static function add( $source_path, $target_url, $status = 301, $match = 'exact', $note = '' ) {
        global $wpdb;
        $row = array(
            'blog_id'     => Seoforge_Utils::current_blog_id(),
            'source_path' => mb_substr( esc_url_raw( $source_path ), 0, 500 ),
            'target_url'  => mb_substr( esc_url_raw( $target_url ), 0, 500 ),
            'status_code' => in_array( (int) $status, array( 301, 302, 307, 308 ), true ) ? (int) $status : 301,
            'match_type'  => in_array( $match, array( 'exact', 'prefix' ), true ) ? $match : 'exact',
            'is_active'   => 1,
            'hits'        => 0,
            'note'        => mb_substr( sanitize_text_field( $note ), 0, 255 ),
            'created_at'  => current_time( 'mysql', 1 ),
        );
        $wpdb->insert( Seoforge_Utils::table( 'redirects' ), $row );
        $id = (int) $wpdb->insert_id;
        if ( $id && class_exists( 'Seoforge_Audit' ) ) {
            Seoforge_Audit::log( 'redirect_created', array(
                'object_type' => 'redirect',
                'object_id'   => (string) $id,
                'summary'     => sprintf( '%s -> %s (%d)', $row['source_path'], $row['target_url'], $row['status_code'] ),
            ) );
        }
        return $id;
    }

    public static function delete( $id ) {
        global $wpdb;
        // Scope to the current blog so a subsite admin cannot delete another
        // subsite's redirect row by guessing its id (Multisite isolation).
        $deleted = (bool) $wpdb->delete(
            Seoforge_Utils::table( 'redirects' ),
            array( 'id' => (int) $id, 'blog_id' => Seoforge_Utils::current_blog_id() )
        );
        if ( $deleted && class_exists( 'Seoforge_Audit' ) ) {
            Seoforge_Audit::log( 'redirect_deleted', array(
                'object_type' => 'redirect',
                'object_id'   => (string) (int) $id,
            ) );
        }
        return $deleted;
    }

    public static function toggle( $id ) {
        global $wpdb;
        $tbl  = Seoforge_Utils::table( 'redirects' );
        $blog = Seoforge_Utils::current_blog_id();
        $cur  = $wpdb->get_var( $wpdb->prepare( "SELECT is_active FROM %i WHERE id = %d AND blog_id = %d", $tbl, (int) $id, $blog ) );
        if ( null === $cur ) {
            return false; // not found / not owned by this blog.
        }
        $next = (int) $cur ? 0 : 1;
        $wpdb->update( $tbl, array( 'is_active' => $next ), array( 'id' => (int) $id, 'blog_id' => $blog ) );
        return $next;
    }

    public static function all( $limit = 200, $offset = 0 ) {
        global $wpdb;
        $tbl = Seoforge_Utils::table( 'redirects' );
        $blog = Seoforge_Utils::current_blog_id();
        return $wpdb->get_results( $wpdb->prepare(
            "SELECT * FROM %i WHERE blog_id = %d ORDER BY id DESC LIMIT %d OFFSET %d",
            $tbl,
            $blog,
            (int) $limit,
            (int) $offset
        ), ARRAY_A );
    }
}
