/**
 * Rule Engine
 * Loads and executes JS rule scripts from the rules/ directory
 */

import fs from 'fs';
import path from 'path';
import { fileURLToPath, pathToFileURL } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const RULES_DIR = path.join(__dirname, 'rules');

let rules = [];
let db = null;
let sendRPCToDevice = null;
let statusUpdateCallback = null;

/**
 * Initialize rule engine with database and RPC function
 */
export function initRuleEngine(database, rpcFunction, onStatusUpdate = null) {
    db = database;
    sendRPCToDevice = rpcFunction;
    statusUpdateCallback = onStatusUpdate;
}

/**
 * Load all rule scripts from rules/ directory
 */
export async function loadRules() {
    rules = [];

    if (!fs.existsSync(RULES_DIR)) {
        console.log('Rules directory not found, creating...');
        fs.mkdirSync(RULES_DIR, { recursive: true });
        return;
    }

    const files = fs.readdirSync(RULES_DIR).filter(f => f.endsWith('.js'));

    for (const file of files) {
        try {
            const filePath = path.join(RULES_DIR, file);
            // Add timestamp to bust cache on reload
            const fileUrl = pathToFileURL(filePath).href + '?t=' + Date.now();
            const module = await import(fileUrl);
            const rule = module.default || module;
            rule._filename = file;
            rules.push(rule);
            console.log(`Loaded rule: ${file}`);
        } catch (err) {
            console.error(`Error loading rule ${file}:`, err);
        }
    }

    console.log(`Loaded ${rules.length} rule(s)`);
}

/**
 * Cast string value to proper type based on channel type
 */
function castValue(value, type) {
    if (value === null || value === undefined) return null;
    switch (type) {
        case 'boolean':
            return value === 'true' || value === true;
        case 'range':
            return parseInt(value, 10);
        case 'enum':
        default:
            return value;
    }
}

/**
 * Get current state of a channel
 */
function getChannelState(mac, component, field) {
    return new Promise((resolve, reject) => {
        db.get(
            `SELECT e.event, c.type FROM events e 
             JOIN channels c ON e.channel_id = c.id 
             WHERE c.mac = ? AND c.component = ? AND c.field = ?
             ORDER BY e.id DESC LIMIT 1`,
            [mac, component, field],
            (err, row) => {
                if (err) reject(err);
                else resolve(row ? castValue(row.event, row.type) : null);
            }
        );
    });
}

/**
 * Get all current channel states
 */
function getAllChannelStates() {
    return new Promise((resolve, reject) => {
        db.all(
            `SELECT c.mac, c.component, c.field, c.type, 
                    (SELECT e.event FROM events e WHERE e.channel_id = c.id ORDER BY e.id DESC LIMIT 1) as value
             FROM channels c`,
            [],
            (err, rows) => {
                if (err) reject(err);
                else resolve(rows || []);
            }
        );
    });
}

/**
 * Create context object passed to rule scripts
 */
function createContext(triggerEvent, ruleName) {
    return {
        // The event that triggered this evaluation
        trigger: triggerEvent,

        // Get state of a specific channel
        getState: getChannelState,

        // Get all channel states
        getAllStates: getAllChannelStates,

        // Set light level (0-100)
        setLevel: async (mac, component, level) => {
            if (sendRPCToDevice) {
                await sendRPCToDevice(mac, 'Light.Set', { id: parseInt(component.split(':')[1]), brightness: level });
            }
        },

        // Set switch output
        setOutput: async (mac, component, on) => {
            if (sendRPCToDevice) {
                await sendRPCToDevice(mac, 'Switch.Set', { id: parseInt(component.split(':')[1]), on: on });
            }
        },

        // Generic RPC call
        sendRPC: async (mac, method, params) => {
            if (sendRPCToDevice) {
                await sendRPCToDevice(mac, method, params);
            }
        },

        // Push status update to dashboard
        updateStatus: (status) => {
            if (statusUpdateCallback) {
                statusUpdateCallback(ruleName, status);
            }
        },

        // Logging
        log: (...args) => console.log('[Rule]', ...args)
    };
}

/**
 * Run all rules after an event occurs
 */
export async function runRules(mac, component, field, type, event) {
    // Cast event value to proper type
    const typedEvent = castValue(event, type);
    const triggerEvent = { mac, component, field, type, event: typedEvent };

    for (const rule of rules) {
        try {
            const ctx = createContext(triggerEvent, rule._filename);
            if (typeof rule.run === 'function') {
                await rule.run(ctx);
            } else if (typeof rule === 'function') {
                await rule(ctx);
            }
        } catch (err) {
            console.error(`Error running rule ${rule._filename || 'unknown'}:`, err);
        }
    }
}

/**
 * Reload rules from disk
 */
export async function reloadRules() {
    console.log('Reloading rules...');
    await loadRules();
}

/**
 * Get status from all rules that have a getStatus() hook
 */
export async function getRulesStatus() {
    const statuses = [];
    for (const rule of rules) {
        try {
            if (typeof rule.getStatus === 'function') {
                const status = await rule.getStatus();
                statuses.push({
                    name: rule._filename,
                    status
                });
            } else {
                statuses.push({
                    name: rule._filename,
                    status: null
                });
            }
        } catch (err) {
            console.error(`Error getting status from rule ${rule._filename}:`, err);
            statuses.push({
                name: rule._filename,
                status: { error: err.message }
            });
        }
    }
    return statuses;
}

/**
 * Set config on a specific rule
 */
export function setRuleConfig(ruleName, key, value) {
    const rule = rules.find(r => r._filename === ruleName);
    if (rule && typeof rule.setConfig === 'function') {
        return rule.setConfig(key, value);
    }
    return false;
}

/**
 * Watch rules directory for changes and auto-reload
 */
export function watchRules() {
    if (!fs.existsSync(RULES_DIR)) {
        fs.mkdirSync(RULES_DIR, { recursive: true });
    }

    let reloadTimeout = null;

    fs.watch(RULES_DIR, (eventType, filename) => {
        if (filename && filename.endsWith('.js')) {
            // Debounce reloads
            if (reloadTimeout) clearTimeout(reloadTimeout);
            reloadTimeout = setTimeout(async () => {
                console.log(`Rule file changed: ${filename}`);
                await reloadRules();
            }, 500);
        }
    });

    console.log('Watching rules/ directory for changes');
}
