<?php
/**
 * WP-CLI commands.
 *
 * Usage:
 *   wp brainwerk-seo-suite stats [--days=30] [--blog=0]
 *   wp brainwerk-seo-suite purge visits|sessions|404
 *   wp brainwerk-seo-suite cleanup
 *   wp brainwerk-seo-suite aggregate
 *   wp brainwerk-seo-suite seed-demo [--days=14] [--per-day=200]
 *   wp brainwerk-seo-suite export <visits|sessions|404|redirects> [--days=30] [--out=-]
 *   wp brainwerk-seo-suite import-redirects <file.csv>
 *
 * `wp seoforge seed-demo` and `wp seoforge import-redirects` also work as
 * short compatibility aliases (registered further down this file) — kept
 * for anyone already scripting against them, not the documented namespace.
 *
 * @package Brainwerk SEO Suite
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}
if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
    return;
}

class Seoforge_CLI {

    /**
     * Show summary stats for the given range.
     *
     * ## OPTIONS
     *
     * [--days=<int>]
     * : How many days back to include. Default 30.
     *
     * [--blog=<int>]
     * : Restrict to a specific blog id (Multisite).
     *
     * @when after_wp_load
     */
    public function stats( $args, $assoc ) {
        $days = isset( $assoc['days'] ) ? (int) $assoc['days'] : 30;
        $blog = isset( $assoc['blog'] ) ? (int) $assoc['blog'] : null;
        $sum  = Seoforge_Stats::summary( $days * 24, $blog );
        WP_CLI::line( "Brainwerk SEO Suite — last {$days} days" );
        WP_CLI::line( str_repeat( '-', 36 ) );
        WP_CLI::line( sprintf( "%-22s %d", 'Pageviews',        $sum['visits'] ) );
        WP_CLI::line( sprintf( "%-22s %d", 'Unique visitors',  $sum['unique_visitors'] ) );
        WP_CLI::line( sprintf( "%-22s %d", 'Sessions',         $sum['sessions'] ) );
        WP_CLI::line( sprintf( "%-22s %.1f%%", 'Bounce rate',  $sum['bounce_rate'] ) );
        WP_CLI::line( sprintf( "%-22s %ds", 'Avg duration',    (int) $sum['avg_duration'] ) );
    }

    /**
     * Purge a table.
     *
     * ## OPTIONS
     * <kind>
     * : One of: visits, sessions, 404
     *
     * @when after_wp_load
     */
    public function purge( $args ) {
        $kind = isset( $args[0] ) ? $args[0] : '';
        switch ( $kind ) {
            case 'visits':
            case 'sessions':
                Seoforge_Tools::reset_visits();
                WP_CLI::success( 'visits + sessions truncated.' );
                break;
            case '404':
                Seoforge_Tools::reset_404();
                WP_CLI::success( '404 log truncated.' );
                break;
            default:
                WP_CLI::error( 'Unknown kind. Use one of: visits, sessions, 404' );
        }
    }

    /**
     * Run the daily cleanup right now — scoped to one blog, like the
     * "Run cleanup now" Tools button and the seoforge_cleanup_visits cron
     * (Multisite: pass --blog, or use `--url=` to select the site; without
     * either, this runs against whatever blog WP-CLI bootstrapped into,
     * normally the main site).
     *
     * ## OPTIONS
     *
     * [--blog=<int>]
     * : Restrict to a specific blog id (Multisite).
     *
     * @when after_wp_load
     */
    public function cleanup( $args, $assoc ) {
        $blog = isset( $assoc['blog'] ) ? (int) $assoc['blog'] : 0;
        if ( $blog && is_multisite() ) {
            switch_to_blog( $blog );
        }
        Seoforge_Tools::cleanup_now();
        if ( $blog && is_multisite() ) {
            restore_current_blog();
        }
        WP_CLI::success( 'Cleanup done.' );
    }

    /**
     * Run the hourly aggregator right now.
     * @when after_wp_load
     */
    public function aggregate( $args, $assoc ) {
        $days = isset( $assoc['days'] ) ? max( 1, (int) $assoc['days'] ) : 2;
        Seoforge_Sessions::aggregate();
        Seoforge_Aggregator::rebuild_range( $days );
        WP_CLI::success( "Sessions + last {$days} days of daily aggregates rebuilt." );
    }

    /**
     * Seed demo data so the dashboard shows something on a fresh install.
     *
     * ## OPTIONS
     *
     * [--days=<int>]
     * : Days of demo history to generate. Default 14.
     *
     * [--per-day=<int>]
     * : Visits per day. Default 200.
     *
     * @when after_wp_load
     */
    public function seed_demo( $args, $assoc ) {
        $days     = isset( $assoc['days'] ) ? max( 1, (int) $assoc['days'] ) : 14;
        $per_day  = isset( $assoc['per-day'] ) ? max( 1, (int) $assoc['per-day'] ) : 200;
        $count    = Seoforge_Demo::seed( $days, $per_day );
        WP_CLI::success( "Seeded {$count} demo visits across {$days} days." );
        Seoforge_Sessions::aggregate();
        Seoforge_Aggregator::rebuild_range( $days + 1 );
        Seoforge_Stats::flush_cache();
        WP_CLI::success( 'Aggregates rebuilt + cache flushed.' );
    }

    /**
     * Export a CSV to stdout (or a file with --out=PATH).
     *
     * ## OPTIONS
     * <kind>
     * : One of: visits, sessions, 404, redirects
     *
     * [--days=<int>]
     * : For visits/sessions, how many days back. Default 30.
     *
     * [--out=<path>]
     * : Write to a file. Default '-' (stdout).
     *
     * @when after_wp_load
     */
    public function export( $args, $assoc ) {
        $kind = isset( $args[0] ) ? $args[0] : '';
        $days = isset( $assoc['days'] ) ? (int) $assoc['days'] : 30;
        $out  = isset( $assoc['out'] ) ? $assoc['out'] : '-';
        $valid = array( 'visits', 'sessions', '404', 'redirects' );
        if ( ! in_array( $kind, $valid, true ) ) {
            WP_CLI::error( 'kind must be one of: ' . implode( ', ', $valid ) );
        }
        if ( '-' !== $out ) {
            // Restrict writes to the WP uploads dir so users can't be tricked
            // into clobbering arbitrary paths via --out=.
            $uploads = wp_upload_dir();
            $base    = isset( $uploads['basedir'] ) ? trailingslashit( $uploads['basedir'] ) . 'brainwerk-seo-suite' : '';
            if ( ! $base ) {
                WP_CLI::error( 'uploads directory unavailable' );
            }
            if ( ! file_exists( $base ) ) {
                wp_mkdir_p( $base );
            }
            // Accept either a bare filename or an absolute/relative path that
            // resolves under the uploads/seoforge directory.
            $filename = wp_basename( $out );
            $filename = sanitize_file_name( $filename );
            if ( '' === $filename ) {
                WP_CLI::error( 'invalid --out filename' );
            }
            $path = trailingslashit( $base ) . $filename;
            $fh = fopen( $path, 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions
            if ( ! $fh ) { WP_CLI::error( 'cannot open output file: ' . $path ); }
            WP_CLI::log( 'writing to ' . $path );
            ob_start( function ( $buf ) use ( $fh ) { fwrite( $fh, $buf ); return ''; }, 4096 );
        }
        // Reuse the HTTP exporter — its stream methods are public-equivalent
        // because php://output is the same descriptor in CLI.
        $reflection = new \ReflectionClass( 'Seoforge_Export' );
        $method = $reflection->getMethod( 'stream_' . $kind );
        $method->setAccessible( true );
        if ( in_array( $kind, array( 'visits', 'sessions' ), true ) ) {
            $method->invoke( null, $days );
        } else {
            $method->invoke( null );
        }
        if ( '-' !== $out ) {
            ob_end_flush();
            fclose( $fh );
            WP_CLI::success( "wrote $out" );
        }
    }

    /**
     * Import redirects from a CSV file.
     *
     * ## OPTIONS
     * <file>
     * : Path to CSV file.
     *
     * @when after_wp_load
     */
    public function import_redirects( $args ) {
        $path = isset( $args[0] ) ? $args[0] : '';
        if ( ! is_readable( $path ) ) {
            WP_CLI::error( 'file not readable: ' . $path );
        }
        $res = Seoforge_Export::import_redirects_csv( $path );
        WP_CLI::success( sprintf( 'imported %d, skipped %d', $res['ok'], $res['fail'] ) );
    }

    /**
     * Show license state.
     * @when after_wp_load
     */
    public function license( $args ) {
        $sub   = isset( $args[0] ) ? $args[0] : 'status';
        $state = Seoforge_License::get();
        if ( 'status' === $sub ) {
            WP_CLI::line( 'tier:        ' . $state['tier'] );
            WP_CLI::line( 'status:      ' . $state['status'] );
            WP_CLI::line( 'sites:       ' . $state['sites_used'] . ' / ' . $state['sites_max'] );
            WP_CLI::line( 'expires:     ' . ( $state['expires_at'] ?: '—' ) );
            WP_CLI::line( 'last_checked:' . ( $state['last_checked'] ?: '—' ) );
            return;
        }
        if ( 'activate' === $sub ) {
            $key = isset( $args[1] ) ? $args[1] : '';
            $r = Seoforge_License::activate( $key );
            if ( is_wp_error( $r ) ) { WP_CLI::error( $r->get_error_message() ); }
            WP_CLI::success( 'activated' );
            return;
        }
        if ( 'deactivate' === $sub ) {
            Seoforge_License::deactivate();
            WP_CLI::success( 'deactivated' );
            return;
        }
        WP_CLI::error( 'sub must be one of: status, activate, deactivate' );
    }
}

WP_CLI::add_command( 'brainwerk-seo-suite', 'Seoforge_CLI' );
// Compatibility aliases only — `wp brainwerk-seo-suite ...` is the documented
// namespace (see docs/1.5-open-decisions.md #5). Kept for older scripts that
// already target `wp seoforge seed-demo` / `wp seoforge import-redirects`.
WP_CLI::add_command( 'seoforge seed-demo', function ( $a, $b ) {
    ( new Seoforge_CLI() )->seed_demo( $a, $b );
} );
WP_CLI::add_command( 'seoforge import-redirects', function ( $a ) {
    ( new Seoforge_CLI() )->import_redirects( $a );
} );
