<?php
namespace Bookaro\Controller\Admin;

defined( 'ABSPATH' ) || exit;

use Bookaro\Utils\Logger;
use Bookaro\Utils\TemplateLoader;
use function add_submenu_page;
use function admin_url;
use function current_user_can;
use function esc_html__;
use function sanitize_key;
use function sanitize_text_field;
use function wp_die;
use function wp_unslash;
use function wp_verify_nonce;

final class LogsController
{
    public function register_hooks(): void
    {
        add_action( 'admin_menu', [ $this, 'add_submenu_page' ], 28 );
    }

    public function add_submenu_page(): void
    {
        $capability = function_exists( 'bookaro_get_admin_menu_capability' )
            ? bookaro_get_admin_menu_capability()
            : 'manage_options';
        $hook = add_submenu_page(
            'bookaro-bookings',
            __( 'Logs', 'mybookaro' ),
            __( 'Logs', 'mybookaro' ),
            $capability,
            'bookaro-logs',
            [ $this, 'render_logs_page' ]
        );

        // Download must stream Content-Disposition + file body BEFORE WP
        // emits the admin chrome; otherwise the headers are ignored and
        // the file bytes leak into the partial HTML, leaving the user on
        // a broken half-page instead of a file download. load-{hook}
        // fires before any output, which is the right moment. Bug 20.
        if ( $hook ) {
            add_action( 'load-' . $hook, [ $this, 'maybe_dispatch_download' ] );
        }
    }

    public function maybe_dispatch_download(): void
    {
        $can_manage_admin = function_exists( 'bookaro_current_user_can_manage_admin' )
            ? bookaro_current_user_can_manage_admin()
            : current_user_can( 'manage_options' );
        if ( ! $can_manage_admin ) {
            return;
        }
        $action = sanitize_key( wp_unslash( $_GET['action'] ?? '' ) );
        if ( $action !== 'download' ) {
            return;
        }
        $nonce = sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) );
        if ( $nonce === '' || ! wp_verify_nonce( $nonce, 'bookaro_download_logs' ) ) {
            return; // let render_logs_page show the "Invalid download request" notice
        }
        Logger::ensure_log_file();
        $path = Logger::get_log_file_path();
        if ( $path === '' || ! file_exists( $path ) || ! is_readable( $path ) ) {
            return; // let render_logs_page show the "Log file not found" notice
        }
        $this->download_log( $path );
    }

    public function render_logs_page(): void
    {
        $can_manage_admin = function_exists( 'bookaro_current_user_can_manage_admin' )
            ? bookaro_current_user_can_manage_admin()
            : current_user_can( 'manage_options' );
        if ( ! $can_manage_admin ) {
            wp_die( esc_html__( 'You do not have permission to access this page.', 'mybookaro' ) );
        }

        Logger::ensure_log_file();

        $notice = '';
        $notice_type = 'success';
        $action = sanitize_key( wp_unslash( $_GET['action'] ?? '' ) );
        $nonce = sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) );
        $path = Logger::get_log_file_path();

        if ( $action === 'clear' && $nonce !== '' ) {
            if ( wp_verify_nonce( $nonce, 'bookaro_clear_logs' ) ) {
                if ( $path !== '' ) {
                    $this->truncate_log( $path );
                }
                $notice = __( 'Logs cleared.', 'mybookaro' );
            } else {
                $notice = __( 'Invalid request. Please try again.', 'mybookaro' );
                $notice_type = 'error';
            }
        }

        if ( $action === 'download' && $nonce !== '' ) {
            // Streaming happens earlier on load-{hook} (maybe_dispatch_download);
            // by the time we reach this render path the only thing left to do
            // is surface the appropriate notice for the cases where load-{hook}
            // declined to stream — invalid nonce or missing log file.
            if ( ! wp_verify_nonce( $nonce, 'bookaro_download_logs' ) ) {
                $notice = __( 'Invalid download request.', 'mybookaro' );
                $notice_type = 'error';
            } elseif ( $path === '' || ! file_exists( $path ) || ! is_readable( $path ) ) {
                $notice = __( 'Log file not found. Enable file logging in Notifications settings first.', 'mybookaro' );
                $notice_type = 'error';
            }
        }

        $per_page = absint( wp_unslash( $_GET['per_page'] ?? ( $_GET['lines'] ?? 400 ) ) );
        if ( $per_page < 50 ) {
            $per_page = 50;
        }
        if ( $per_page > 2000 ) {
            $per_page = 2000;
        }
        $current_page = absint( wp_unslash( $_GET['paged'] ?? 1 ) );
        if ( $current_page < 1 ) {
            $current_page = 1;
        }

        $enabled = Logger::is_file_logging_enabled();
        $file_exists = $path !== '' && file_exists( $path );
        $readable = $file_exists && is_readable( $path );
        $total_lines = $readable ? $this->count_log_lines( $path ) : 0;
        $total_pages = $total_lines > 0 ? (int) ceil( $total_lines / $per_page ) : 1;
        if ( $total_pages < 1 ) {
            $total_pages = 1;
        }
        if ( $current_page > $total_pages ) {
            $current_page = $total_pages;
        }

        $entries = [];
        if ( $readable ) {
            $tail_lines = $this->read_log_tail( $path, $per_page * $current_page );
            $tail_count = count( $tail_lines );
            $start = max( 0, $tail_count - ( $per_page * $current_page ) );
            $end = $tail_count - ( $per_page * ( $current_page - 1 ) );
            $length = max( 0, $end - $start );
            if ( $length > 0 ) {
                $entries = array_slice( $tail_lines, $start, $length );
            }
        }
        $file_size = $file_exists ? filesize( $path ) : 0;
        $file_mtime = $file_exists ? filemtime( $path ) : 0;

        TemplateLoader::render( 'admin/logs.php', [
            'notice' => $notice,
            'notice_type' => $notice_type,
            'enabled' => $enabled,
            'path' => $path,
            'file_exists' => $file_exists,
            'readable' => $readable,
            'entries' => $entries,
            'per_page' => $per_page,
            'current_page' => $current_page,
            'total_pages' => $total_pages,
            'total_lines' => $total_lines,
            'file_size' => $file_size,
            'file_mtime' => $file_mtime,
            'settings_url' => admin_url( 'admin.php?page=bookaro-booking-settings&tab=notifications#bookaro-settings-tabs' ),
        ] );
    }

    private function truncate_log( string $path ): void
    {
        if ( $path === '' ) {
            return;
        }
        $dir = dirname( $path );
        if ( ! is_dir( $dir ) ) {
            wp_mkdir_p( $dir );
        }
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- Bookaro's log writer needs a direct, fast permission probe on the log directory. The WP_Filesystem API would force credential prompts in some environments and add async/file-system overhead that's unnecessary for a one-line "can I write here?" check that runs on every log emission.
        if ( is_dir( $dir ) && is_writable( $dir ) ) {
            file_put_contents( $path, '' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- Direct write to Bookaro's own log file; WP_Filesystem would force credential prompts and adds overhead unsuited to log I/O.
        }
    }

    private function download_log( string $path ): void
    {
        if ( $path === '' || ! file_exists( $path ) || ! is_readable( $path ) ) {
            wp_die( esc_html__( 'Log file not found.', 'mybookaro' ) );
        }
        $filename = 'mybookaro-logs-' . gmdate( 'Ymd-His' ) . '.log';
        header( 'Content-Type: text/plain; charset=utf-8' );
        header( 'Content-Disposition: attachment; filename=' . $filename );
        header( 'Content-Length: ' . (string) filesize( $path ) );
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Streaming the log file to the response body. WP_Filesystem::get_contents() would load the entire file into memory (potentially many MB for verbose installs) before sending. readfile() streams chunk-by-chunk, which is the appropriate primitive for a download endpoint.
        readfile( $path );
        exit;
    }

    private function read_log_tail( string $path, int $max_lines ): array
    {
        if ( ! file_exists( $path ) || ! is_readable( $path ) || $max_lines < 1 ) {
            return [];
        }

        $size = filesize( $path );
        if ( $size === 0 || $size === false ) {
            return [];
        }

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- We need fseek() + fgetc() to walk backward from EOF and read the tail without loading the whole log into memory. WP_Filesystem doesn't expose a byte-level seek API; using get_contents() would defeat the purpose of "tail N lines from a large file."
        $handle = fopen( $path, 'rb' );
        if ( ! $handle ) {
            return [];
        }

        $lines = [];
        $buffer = '';
        $pos = -1;

        while ( -$pos <= $size ) {
            fseek( $handle, $pos, SEEK_END );
            $char = fgetc( $handle );
            if ( $char === "\n" ) {
                if ( $buffer !== '' ) {
                    $lines[] = strrev( $buffer );
                    $buffer = '';
                    if ( count( $lines ) >= $max_lines ) {
                        break;
                    }
                }
            } else {
                $buffer .= $char;
            }
            $pos--;
        }

        if ( $buffer !== '' && count( $lines ) < $max_lines ) {
            $lines[] = strrev( $buffer );
        }

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Pair of the fopen() above; closing the local file handle. WP_Filesystem doesn't apply here.
        fclose( $handle );
        return array_reverse( $lines );
    }

    private function count_log_lines( string $path ): int
    {
        if ( $path === '' || ! file_exists( $path ) || ! is_readable( $path ) ) {
            return 0;
        }

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Streaming line count over a potentially large log file. See read_log_tail() for the same justification: WP_Filesystem has no byte-streaming API.
        $handle = fopen( $path, 'rb' );
        if ( ! $handle ) {
            return 0;
        }

        $count = 0;
        while ( ! feof( $handle ) ) {
            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread -- 8 KB chunked read to count newlines without loading the file into memory.
            $buffer = fread( $handle, 8192 );
            if ( $buffer === false ) {
                break;
            }
            $count += substr_count( $buffer, "\n" );
        }

        $stat = fstat( $handle );
        if ( $stat && ! empty( $stat['size'] ) ) {
            if ( fseek( $handle, -1, SEEK_END ) === 0 ) {
                $last_char = fgetc( $handle );
                if ( $last_char !== "\n" ) {
                    $count++;
                }
            }
        }

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Pair of the fopen() above.
        fclose( $handle );

        return $count;
    }
}
