// src/pages/admin/components/SystemCheck.tsx
/// <reference types="vite/client" />
import React, { useState, useEffect, useCallback } from 'react';
import toast from 'react-hot-toast';
import { loginUser, pingBackend, checkDbSchema, checkStorage, checkDbPoolHealth, checkPm2Status, checkRedisHealth, triggerFailingJob, clearGeocodeCache } from '../../../services/apiClient';
import { ShieldCheckIcon } from '../../../components/icons/ShieldCheckIcon';
import { LoadingSpinner } from '../../../components/LoadingSpinner';
import { CheckCircleIcon } from '../../../components/icons/CheckCircleIcon';
import { XCircleIcon } from '../../../components/icons/XCircleIcon';
import { BeakerIcon } from 'lucide-react';

type TestStatus = 'idle' | 'running' | 'pass' | 'fail';

// Using an enum for check IDs improves type safety and autocompletion.
enum CheckID {
    GEMINI = 'gemini',
    BACKEND = 'backend',
    SCHEMA = 'schema',
    DB_POOL = 'db_pool',
    SEED = 'seed',
    STORAGE = 'storage',
    REDIS = 'redis',
    PM2_STATUS = 'pm2_status', // Restoring PM2 Status check
}

interface Check {
  id: CheckID;
  name: string;
  description: string;
  status: TestStatus;
  message: string;
}

const initialChecks: Check[] = [
    { id: CheckID.BACKEND, name: 'Backend Server Connection', description: 'Checks if the local Express.js server is running and reachable.', status: 'idle', message: '' },
    { id: CheckID.PM2_STATUS, name: 'PM2 Process Status', description: 'Checks if the application is running under PM2.', status: 'idle', message: '' }, // Restoring PM2 Status check
    { id: CheckID.DB_POOL, name: 'Database Connection Pool', description: 'Checks the health of the database connection pool.', status: 'idle', message: '' },
    { id: CheckID.REDIS, name: 'Redis Connection', description: 'Checks if the backend can connect to the Redis server, used for background jobs and caching.', status: 'idle', message: '' },
    { id: CheckID.SCHEMA, name: 'Database Schema', description: 'Verifies required tables exist in the database.', status: 'idle', message: '' },
    { id: CheckID.SEED, name: 'Default Admin User', description: 'Verifies the default admin user can be logged into.', status: 'idle', message: '' },
    { id: CheckID.STORAGE, name: 'Assets Storage Directory', description: 'Checks if the local assets folder exists and is writable.', status: 'idle', message: '' },
    { id: CheckID.GEMINI, name: 'Gemini API Key', description: 'Verifies the GEMINI_API_KEY is set for AI features.', status: 'idle', message: '' },
];

interface GeocodeCacheManagerProps {
  redisOk: boolean;
}

const GeocodeCacheManager: React.FC<GeocodeCacheManagerProps> = ({ redisOk }) => {
  const [isLoading, setIsLoading] = useState(false);

  const handleClearCache = async () => {
    if (!window.confirm('Are you sure you want to clear the entire geocoding cache? This action cannot be undone.')) {
      return;
    }

    setIsLoading(true);
    try {
      const response = await clearGeocodeCache();
      const data = await response.json();

      if (!response.ok) {
        throw new Error(data.message || 'An unknown error occurred.');
      }

      toast.success(data.message);
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Failed to clear cache.';
      toast.error(errorMessage);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div className="mt-4">
      <div className="flex items-center space-x-2">
        <h4 className="font-medium text-gray-800 dark:text-gray-200">Geocoding Service</h4>
        {redisOk && <CheckCircleIcon className="w-5 h-5 text-green-500" title="Redis cache is connected" />}
      </div>
      <p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
        The application uses a Redis cache to store geocoding results and reduce API calls. You can manually clear this cache if you suspect the data is stale.
      </p>
      {redisOk && (
        <button
          onClick={handleClearCache}
          disabled={isLoading}
          className="mt-3 inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 disabled:bg-red-400"
        >
          {isLoading ? (
            <>
              <div className="w-5 h-5 mr-2"><LoadingSpinner /></div>
              <span>Clearing...</span>
            </>
          ) : ( 'Clear Geocode Cache' )}
        </button>
      )}
    </div>
  );
};

export const SystemCheck: React.FC = () => {
    const [checks, setChecks] = useState<Check[]>(initialChecks);
    const [isRunning, setIsRunning] = useState(false);
    const [hasRunAutoTest, setHasRunAutoTest] = useState(false);
    const [elapsedTime, setElapsedTime] = useState<number | null>(null);
    const [isTriggeringJob, setIsTriggeringJob] = useState(false);
    const [redisOk, setRedisOk] = useState(false);
    
    const updateCheckStatus = useCallback((id: CheckID, status: TestStatus, message: string) => {
        setChecks(prev => prev.map(c => c.id === id ? { ...c, status, message } : c));
    }, []);

    // Helper to centralize error message parsing.
    const getErrorMessage = (error: unknown): string => {
        return error instanceof Error ? error.message : String(error);
    };

    const checkApiKey = useCallback(() => {
        if (import.meta.env.GEMINI_API_KEY) {
            updateCheckStatus(CheckID.GEMINI, 'pass', 'GEMINI_API_KEY is set.');
            return true;
        } else {
            updateCheckStatus(CheckID.GEMINI, 'fail', 'GEMINI_API_KEY is missing. AI features will not work.');
            return false;
        }
    }, [updateCheckStatus]);

    const checkBackendConnection = useCallback(async () => {
        try {
            const response = await pingBackend();
            if (response.ok) {
                const text = await response.text();
                if (text === 'pong') {
                updateCheckStatus(CheckID.BACKEND, 'pass', 'Backend server is running and reachable.');
                return true;
                }
            }
            throw new Error("Backend server is not responding. Is it running?");
        } catch (e) {
            updateCheckStatus(CheckID.BACKEND, 'fail', getErrorMessage(e));
            return false;
        }
    }, [updateCheckStatus]);

    const checkPm2Process = useCallback(async () => {
        try {
            const response = await checkPm2Status();
            if (!response.ok) throw new Error((await response.json()).message || 'Failed to get PM2 status');
            const { success, message } = await response.json();
            updateCheckStatus(CheckID.PM2_STATUS, success ? 'pass' : 'fail', message);
            return success;
        } catch (e) {
            updateCheckStatus(CheckID.PM2_STATUS, 'fail', getErrorMessage(e));
            return false;
        }
    }, [updateCheckStatus]); // Removed checkPm2Status from dependency array as it's an apiClient function

    const checkDatabaseSchema = useCallback(async () => {
        try {
            const response = await checkDbSchema();
            if (!response.ok) throw new Error((await response.json()).message || 'Failed to check DB schema');
            const { success, message } = await response.json();
            updateCheckStatus(CheckID.SCHEMA, success ? 'pass' : 'fail', message);
            return success;
        } catch (e) {
            updateCheckStatus(CheckID.SCHEMA, 'fail', getErrorMessage(e));
            return false;
        }
    }, [updateCheckStatus]); // Removed checkDbSchema from dependency array

    const checkDatabasePool = useCallback(async () => {
        try {
            const response = await checkDbPoolHealth();
            if (!response.ok) throw new Error((await response.json()).message || 'Failed to check DB pool health');
            const { success, message } = await response.json();
            updateCheckStatus(CheckID.DB_POOL, success ? 'pass' : 'fail', message);
            return success;
        } catch (e) {
            updateCheckStatus(CheckID.DB_POOL, 'fail', getErrorMessage(e));
            return false;
        }
    }, [updateCheckStatus]); // Removed checkDbPoolHealth from dependency array

    const checkRedisConnection = useCallback(async () => {
        try {
            const response = await checkRedisHealth();
            if (!response.ok) throw new Error((await response.json()).message || 'Failed to check Redis health');
            const { success, message } = await response.json();
            updateCheckStatus(CheckID.REDIS, success ? 'pass' : 'fail', message);
            setRedisOk(success);
            return success;
        } catch (e) {
            updateCheckStatus(CheckID.REDIS, 'fail', getErrorMessage(e));
            return false;
        }
    }, [updateCheckStatus]);
    const checkStorageDirectory = useCallback(async () => {
        try {
            const response = await checkStorage();
            if (!response.ok) throw new Error((await response.json()).message || 'Failed to check storage');
            const { success, message } = await response.json();
            updateCheckStatus(CheckID.STORAGE, success ? 'pass' : 'fail', message);
            return success;
        } catch (e) {
            updateCheckStatus(CheckID.STORAGE, 'fail', getErrorMessage(e));
            return false;
        }
    }, [updateCheckStatus]);

    const checkSeededUsers = useCallback(async () => {
        // The loginUser function returns a Response object, which we need to check for success.
        try {
            const response = await loginUser('admin@example.com', 'password123', false);
            if (!response.ok) throw new Error((await response.json()).message || 'Login failed');
            updateCheckStatus(CheckID.SEED, 'pass', 'Default admin user login was successful.');
            return true;
        } catch (e) {
            const errorMessage = getErrorMessage(e);
            const message = errorMessage.includes('Incorrect email or password')
                ? "Login failed. Ensure the default admin user is seeded in your database."
                : `Failed: ${errorMessage}`;
            updateCheckStatus(CheckID.SEED, 'fail', message);
            return false;
        }
    }, [updateCheckStatus]);

    const runChecks = useCallback(async () => {
        const startTime = performance.now();
        setElapsedTime(null); // Reset timer on new run
        setIsRunning(true);
        setChecks(prev => prev.map(c => ({ ...c, status: 'running', message: 'Checking...' })));
    
        try {
            // --- Step 1: Critical backend connection check ---
            const backendOk = await checkBackendConnection();
            if (!backendOk) {
                // If backend is down, fail all dependent checks to provide immediate feedback.
                const dependentChecks = [CheckID.PM2_STATUS, CheckID.DB_POOL, CheckID.REDIS, CheckID.SCHEMA, CheckID.SEED, CheckID.STORAGE];
                dependentChecks.forEach(id => {
                    updateCheckStatus(id, 'fail', 'Skipped: Backend server is not reachable.');
                });
                // Still check the API key as it's a frontend check.
                checkApiKey();
                return; // Exit early
            }

            // --- Step 2: Check PM2 and DB Pool sequentially as they are critical infrastructure. ---
            await checkPm2Process(); // Run PM2 check.
            const dbPoolOk = await checkDatabasePool(); // Run DB Pool check.
            const redisOk = await checkRedisConnection(); // Run Redis check.

            if (!dbPoolOk) {
                // If DB pool is down, skip checks that depend on a DB connection.
                updateCheckStatus(CheckID.SCHEMA, 'fail', 'Skipped: Database connection pool is unhealthy.');
                updateCheckStatus(CheckID.SEED, 'fail', 'Skipped: Database connection pool is unhealthy.');
            }
            // No checks currently depend on Redis, so no skipping logic is needed here.
            
            // If Redis is not OK, we can update the UI accordingly.
            if (!redisOk) {
                // You could add logic here to disable Redis-dependent features in the UI if needed.
            }

            // --- Step 3: Run remaining, less-dependent checks in parallel. ---
            await Promise.all([
                dbPoolOk ? checkDatabaseSchema() : Promise.resolve(),
                dbPoolOk ? checkSeededUsers() : Promise.resolve(),
                checkStorageDirectory(),
                checkApiKey(),
            ]);
        } finally {
            // This block will run regardless of whether the checks succeeded or failed.
            setIsRunning(false);
            const endTime = performance.now();
            setElapsedTime((endTime - startTime) / 1000); // Set elapsed time in seconds
        }
    }, [checkApiKey, checkBackendConnection, checkPm2Process, checkDatabasePool, checkRedisConnection, checkDatabaseSchema, checkStorageDirectory, checkSeededUsers, updateCheckStatus]);

    const handleTriggerFailingJob = async () => {
        setIsTriggeringJob(true);
        try {
            const response = await triggerFailingJob();
            if (!response.ok) {
                const errorData = await response.json();
                throw new Error(errorData.message || 'Failed to trigger job.');
            }
            const data = await response.json();
            toast.success(data.message);
        } catch (error) {
            toast.error(error instanceof Error ? error.message : 'An unknown error occurred.');
        } finally {
            setIsTriggeringJob(false);
        }
    };

    useEffect(() => {
        if (!hasRunAutoTest) {
            setHasRunAutoTest(true);
            runChecks();
        }
    }, [hasRunAutoTest, runChecks]);

    const getStatusIndicator = (status: TestStatus) => {
        switch (status) {
            case 'running': return <div className="w-5 h-5 text-blue-500"><LoadingSpinner /></div>;
            case 'pass': return <CheckCircleIcon className="w-5 h-5 text-green-500" />;
            case 'fail': return <XCircleIcon className="w-5 h-5 text-red-500" />;
            case 'idle': return <div className="w-5 h-5 rounded-full border-2 border-gray-400 dark:border-gray-600"></div>;
            default: return null;
        }
    };

    return (
        <div className="bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
            <h3 className="text-lg font-bold text-gray-800 dark:text-white flex items-center mb-3">
                <ShieldCheckIcon className="w-6 h-6 mr-2 text-brand-primary" />
                System Check
            </h3>
            <p className="text-sm text-gray-600 dark:text-gray-400 mb-5">
                This checklist verifies your local development environment setup.
            </p>
            
            <ul className="space-y-3 mb-4">
                {checks.map(check => (
                    <li key={check.id} className="flex items-start space-x-3">
                        <div className="shrink-0 pt-0.5">{getStatusIndicator(check.status)}</div>
                        <div>
                            <p className="text-sm font-semibold text-gray-800 dark:text-gray-200">{check.name}</p>
                            <p className={`text-xs whitespace-pre-wrap ${check.status === 'fail' ? 'text-red-600 dark:text-red-400' : 'text-gray-500 dark:text-gray-400'}`}>
                                {check.message}
                            </p>
                        </div>
                    </li>
                ))}
            </ul>

            <div className="mt-5 flex items-center justify-between">
                {elapsedTime !== null && !isRunning && (
                    <p className="text-xs text-gray-500 dark:text-gray-400">
                        Finished in {elapsedTime.toFixed(2)} seconds.
                    </p>
                )}
                <button
                    onClick={runChecks}
                    disabled={isRunning}
                    className="w-full bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-wait text-gray-800 dark:text-white font-bold py-2 px-4 rounded-lg transition-colors duration-300 flex items-center justify-center ml-auto max-w-xs"
                >
                    {isRunning ? (
                        <>
                            <div className="w-5 h-5 mr-2"><LoadingSpinner /></div>
                            Running Checks...
                        </>
                    ) : (
                        'Re-run Checks'
                    )}
                </button>
            </div>

            {/* New section for integration confirmation */}
            <div className="mt-8 border-t border-gray-200 dark:border-gray-700 pt-6">
                <h3 className="text-lg font-bold text-gray-800 dark:text-white flex items-center mb-3">
                    <BeakerIcon className="w-6 h-6 mr-2 text-brand-primary" />
                    Confirm Integration: Job Queue Retries
                </h3>
                <div className="text-sm text-gray-600 dark:text-gray-400 space-y-2">
                    <p>Use this to test the background job queue's retry mechanism and the Bull Board UI.</p>
                    <ol className="list-decimal list-inside space-y-1 pl-2">
                        <li>Click the button below to enqueue a job that is designed to fail.</li>
                        <li>Navigate to the <a href="/api/admin/jobs" target="_blank" rel="noopener noreferrer" className="text-brand-primary hover:underline">Job Queue Dashboard</a>.</li>
                        <li>Observe the job in the 'analytics-reporting' queue. It will become active, then fail and move to 'delayed' for its first retry.</li>
                        <li>After the final attempt, it will move to the 'failed' list.</li>
                        <li>In the 'failed' list, a "Retry" button will appear, allowing you to manually trigger the job again.</li>
                    </ol>
                </div>
                <div className="mt-4">
                    <button
                        onClick={handleTriggerFailingJob}
                        disabled={isTriggeringJob}
                        className="bg-red-600 hover:bg-red-700 disabled:opacity-50 disabled:cursor-wait text-white font-bold py-2 px-4 rounded-lg transition-colors duration-300 flex items-center justify-center"
                    >
                        {isTriggeringJob ? (
                            <><div className="w-5 h-5 mr-2"><LoadingSpinner /></div> Triggering...</>
                        ) : 'Trigger Failing Job'}
                    </button>
                </div>
                <GeocodeCacheManager redisOk={redisOk}/>
            </div>
        </div>
    );
};
