<?php

declare(strict_types=1);

namespace SecurWP\Modules\Scanner\Service;

use SecurWP\Modules\Scanner\Repository\FileIndexRepository;

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

/**
 * Encapsulates logic for determining the detailed status of a scan job.
 * Calculates progress and individual component states.
 */
class ScanStatusService {

    /**
     * Get status of specific components/slugs in the current queue.
     *
     * @param array $queue Current queue data
     * @return array Map of [slug => status] (pending, active, complete, failed)
     */
    public function get_component_status( $queue ) {
        if ( ! $queue ) {
            return [];
        }

        // If scan completed, all components are complete
        if ( $queue['status'] === 'complete' ) {
            return $this->get_all_complete_status();
        }

        // Start with default status and then overlay durable queue component statuses.
        $status_map = $this->get_initial_status_map();

        $scan_id = $queue['id'] ?? '';

        // DB queue state is the source of truth for system check status.
        $system_checks = [ 'reputation', 'users', 'content', 'sensitive_data', 'vulnerabilities' ];
        foreach ( $system_checks as $slug ) {
            $resolved = $this->extract_component_status( $queue, $slug );
            if ( $resolved !== null ) {
                $status_map[ $slug ] = $resolved;
            }
        }

        $fsm_progress = $this->get_fsm_progress( $scan_id );
        
        // Complete only when file indexing is finished and no files remain in hashing states.
        $hashing_remaining = ($fsm_progress['by_state']['hashing'] ?? 0)
                           + ($fsm_progress['by_state']['hash_complete'] ?? 0)
                           + ($fsm_progress['by_state']['discovered'] ?? 0);
        $index_repo = new FileIndexRepository();
        $transient_key = 'securwp_file_index_complete_' . $scan_id;
        $file_index_complete = (bool) get_transient( $transient_key );
        if ( ! $file_index_complete && $scan_id ) {
            $legacy_key = 'securwp_discovery_complete_' . $scan_id;
            if ( get_transient( $legacy_key ) ) {
                set_transient( $transient_key, true, HOUR_IN_SECONDS );
                $file_index_complete = true;
            } else {
                if ( $index_repo->is_complete( $scan_id ) ) {
                    set_transient( $transient_key, true, HOUR_IN_SECONDS );
                    $file_index_complete = true;
                }
            }
        }

        // File Scan (pipeline-wide): derive from file index completion + terminal FSM states.
        // This intentionally does NOT depend on queue status so file scanning can appear
        // complete while slower system checks continue.
        $file_index_total = (int) $index_repo->get_total_files( $scan_id );
        $fsm_total = (int) ( $fsm_progress['total'] ?? 0 );
        $file_total = max( $file_index_total, $fsm_total );

        $file_terminal_count = ($fsm_progress['by_state']['completed'] ?? 0)
                             + ($fsm_progress['by_state']['flagged'] ?? 0)
                             + ($fsm_progress['by_state']['error'] ?? 0)
                             + ($fsm_progress['by_state']['verified'] ?? 0)
                             + ($fsm_progress['by_state']['safe'] ?? 0)
                             + ($fsm_progress['by_state']['scan_skipped'] ?? 0);

        if ( $queue['status'] === 'failed' && ! $file_index_complete && $file_total === 0 ) {
            $status_map['file_scan'] = 'failed';
        } elseif ( ! $file_index_complete ) {
            $status_map['file_scan'] = 'active';
        } elseif ( $file_total === 0 || $file_terminal_count >= $file_total ) {
            $status_map['file_scan'] = 'complete';
        } else {
            $status_map['file_scan'] = 'active';
        }

        if ( ! $file_index_complete ) {
            // File indexing still running; keep file changes active even if hash queue is temporarily empty.
            $status_map['file_changes'] = 'active';
        } elseif ( $hashing_remaining === 0 && $fsm_progress['total'] > 0 ) {
            // All files have moved past hashing phase
            $status_map['file_changes'] = 'complete';
        } elseif ( $fsm_progress['total'] === 0 ) {
            // No files discovered yet - still analyzing
            $status_map['file_changes'] = 'active';
        } else {
            $status_map['file_changes'] = 'active';
        }

        $pending_upload = ($fsm_progress['by_state']['pending_upload'] ?? 0);
        $uploading      = ($fsm_progress['by_state']['uploading'] ?? 0);
        $uploaded       = ($fsm_progress['by_state']['uploaded'] ?? 0);
        $cloud_in_progress = $pending_upload + $uploading + $uploaded;
        
        $cloud_completed = ($fsm_progress['by_state']['completed'] ?? 0)
                         + ($fsm_progress['by_state']['flagged'] ?? 0);
        $local_resolved = ($fsm_progress['by_state']['verified'] ?? 0)
                        + ($fsm_progress['by_state']['safe'] ?? 0);
        
        if ( $queue['status'] === 'complete' ) {
            $status_map['cloud_ai'] = 'complete';
        } elseif ( $status_map['file_changes'] !== 'complete' ) {
                if ( $cloud_in_progress > 0 ) {
                $status_map['cloud_ai'] = 'active';
            } else {
                $status_map['cloud_ai'] = 'pending';
            }
        } else {
            if ( $cloud_in_progress === 0 ) {
                $status_map['cloud_ai'] = 'complete';
            } else {
                $status_map['cloud_ai'] = 'active';
            }
        }

        return $status_map;
    }

    /**
     * Get FSM-based progress from file_states table.
     *
     * @param string $scan_id
     * @return array Progress with by_state breakdown
     */
    private function get_fsm_progress( string $scan_id ): array {
        global $wpdb;
        $table = $this->table_identifier( $wpdb->prefix . 'securwp_file_states' );
        
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Prepared repository query.
        $results = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT state, COUNT(*) as count FROM {$table} WHERE scan_id = %s GROUP BY state",
                $scan_id
            ),
            ARRAY_A
        );
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
        
        $progress = [
            'total'    => 0,
            'by_state' => [],
        ];
        
        foreach ( $results as $row ) {
            $state = $row['state'];
            $count = (int) $row['count'];
            $progress['by_state'][ $state ] = $count;
            $progress['total'] += $count;
        }
        
        return $progress;
    }

    /**
     * Get progress percentage using FSM-based terminal state counting.
     * 
     * @param array $queue
     * @return array Progress data with is_analyzing flag
     */
    public function get_progress( $queue ) {
        if ( !$queue ) {
            return [
                'processed'    => 0,
                'total'        => 0,
                'percentage'   => 0,
                'is_analyzing' => true,
            ];
        }

        $scan_id = $queue['id'] ?? '';
        if ( empty( $scan_id ) ) {
            return [
                'processed'    => 0,
                'total'        => 0,
                'percentage'   => 0,
                'is_analyzing' => true,
            ];
        }

        // Get FSM-based progress
        $fsm_progress = $this->get_fsm_progress( $scan_id );
        $index_repo = new FileIndexRepository();
        $file_index_total = $index_repo->get_total_files( $scan_id );
        $total = max( (int) $fsm_progress['total'], $file_index_total );
        $by_state = $fsm_progress['by_state'];

        // If no files discovered yet, show analyzing state
        if ( $total === 0 ) {
            return [
                'processed'    => 0,
                'total'        => 0,
                'percentage'   => 0,
                'is_analyzing' => true,
            ];
        }

        // Count files in terminal states (completed/resolved)
        $terminal = ($by_state['completed'] ?? 0)
                  + ($by_state['flagged'] ?? 0)
                  + ($by_state['error'] ?? 0)
                  + ($by_state['verified'] ?? 0)
                  + ($by_state['safe'] ?? 0)
                  + ($by_state['scan_skipped'] ?? 0);

        $percentage = (int) round( ( $terminal / $total ) * 100 );

        // Check if we have FSM details available
        $fsm_details = [];
        // Calculate details from by_state if not already present in fsm_progress
        $by_state = $fsm_progress['by_state'];
        
        $local = ($by_state['discovered'] ?? 0) 
               + ($by_state['hashing'] ?? 0) 
               + ($by_state['hash_complete'] ?? 0);

        $queue = ($by_state['cloud_hash_check'] ?? 0)
               + ($by_state['pending_upload'] ?? 0);

        $cloud_active = ($by_state['uploading'] ?? 0)
                      + ($by_state['uploaded'] ?? 0) 
                      + ($by_state['scan_requested'] ?? 0)
                      + ($by_state['scanning'] ?? 0);

        // files completed cloud scan
        $cloud_analyzed = ($by_state['completed'] ?? 0)
                        + ($by_state['flagged'] ?? 0)
                        + ($by_state['malicious_detected'] ?? 0);

        // files gathered for cloud scan (Queue + Active + Done)
        // This strictly tracks files that required cloud analysis
        $cloud_gathered = $cloud_analyzed 
                        + $queue 
                        + $cloud_active;

        $flagged_count = ($by_state['flagged'] ?? 0) 
                       + ($by_state['malicious_detected'] ?? 0);

        $fsm_details = [
            'local' => $local,
            'queue' => $queue,
            'cloud' => $cloud_active,
            'flagged' => $flagged_count,
            'cloud_analyzed' => $cloud_analyzed, 
            'cloud_gathered' => $cloud_gathered, 
        ];

        return [
            'processed'    => $terminal,
            'total'        => $total,
            'percentage'   => $percentage,
            'is_analyzing' => $percentage < 100,
            'fsm_details'  => $fsm_details, // Detailed status
        ];
    }

    /**
     * Check if scan is stuck.
     *
     * @param array $queue
     * @param int $threshold Seconds
     * @return bool
     */
    public function is_stuck( $queue, $threshold = 45 ) {
        if ( !$queue ) return false;

        // Don't consider newly created scans as stuck
        $created_at = $queue['created_at'] ?? 0;
        if ( $created_at > 0 && ( time() - $created_at ) < $threshold ) {
            return false; // Scan is too new to be stuck
        }

        // Check if last heartbeat is too old
        $last_ping = $queue['last_ping'] ?? 0;
        if ( $last_ping === 0 ) {
            return false; // No heartbeat yet, not stuck (worker hasn't started)
        }

        return ( time() - $last_ping ) > $threshold;
    }

    private function get_initial_status_map() {
        return [
            'file_scan'       => 'pending',
            'file_changes'    => 'pending',
            'cloud_ai'        => 'pending',
            'reputation'      => 'pending',
            'users'           => 'pending',
            'content'         => 'pending',
            'sensitive_data'  => 'pending',
            'vulnerabilities' => 'pending',
        ];
    }
    
    public function get_all_complete_status() {
         return [
            'file_scan'       => 'complete',
            'file_changes'    => 'complete',
            'cloud_ai'        => 'complete',
            'reputation'      => 'complete',
            'users'           => 'complete',
            'content'         => 'complete',
            'sensitive_data'  => 'complete',
            'vulnerabilities' => 'complete'
        ];
    }

    /**
     * Extract component status from queue component_status payload.
     *
     * Supports both legacy string maps and normalized array maps.
     *
     * @param array $queue Queue payload
     * @param string $slug Component slug
     * @return string|null
     */
    private function extract_component_status( array $queue, string $slug ): ?string {
        $component_status = $queue['component_status'] ?? null;
        if ( ! is_array( $component_status ) || ! isset( $component_status[ $slug ] ) ) {
            return null;
        }

        $entry = $component_status[ $slug ];
        if ( is_string( $entry ) && $entry !== '' ) {
            return $entry;
        }

        if ( is_array( $entry ) && isset( $entry['status'] ) && is_string( $entry['status'] ) && $entry['status'] !== '' ) {
            return $entry['status'];
        }

        return null;
    }

	/**
	 * Quote a table identifier after validating its shape.
	 */
	private function table_identifier( string $table ): string {
		if ( $table === '' || strlen( $table ) > 64 || ! preg_match( '/^[A-Za-z0-9_]+$/', $table ) ) {
			throw new \InvalidArgumentException( 'Invalid table identifier.' );
		}

		return '`' . $table . '`';
	}
}
