<?php

namespace App\Controllers\Rule;

use App\Controllers\BaseController;
use App\Models\Rule\RuleDefModel;
use App\Models\Test\TestDefSiteModel;
use App\Services\RuleExpressionService;
use App\Traits\PatchValidationTrait;
use App\Traits\ResponseTrait;

class RuleController extends BaseController
{
    use ResponseTrait;
    use PatchValidationTrait;

    protected RuleDefModel $ruleDefModel;

    public function __construct()
    {
        $this->ruleDefModel = new RuleDefModel();
    }

    public function index()
    {
        try {
            $eventCode = $this->request->getGet('EventCode');
            $testSiteID = $this->request->getGet('TestSiteID');
            $search = $this->request->getGet('search');

            $builder = $this->ruleDefModel->where('ruledef.EndDate', null);

            if ($eventCode !== null && $eventCode !== '') {
                $builder->where('ruledef.EventCode', $eventCode);
            }
            if ($search !== null && $search !== '') {
                $builder->like('ruledef.RuleName', $search);
            }

            // Filter by TestSiteID - join with mapping table
            if ($testSiteID !== null && $testSiteID !== '' && is_numeric($testSiteID)) {
                $builder->join('testrule', 'testrule.RuleID = ruledef.RuleID', 'inner');
                $builder->where('testrule.TestSiteID', (int) $testSiteID);
                $builder->where('testrule.EndDate IS NULL');
            }

            $rows = $builder
                ->orderBy('ruledef.RuleID', 'ASC')
                ->findAll();

            return $this->respond([
                'status' => 'success',
                'message' => 'fetch success',
                'data' => $rows,
            ], 200);
        } catch (\Throwable $e) {
            log_message('error', 'RuleController::index error: ' . $e->getMessage());
            return $this->respond([
                'status' => 'failed',
                'message' => 'Failed to fetch rules',
                'data' => [],
            ], 500);
        }
    }

    public function show($id = null)
    {
        try {
            if (!$id || !is_numeric($id)) {
                return $this->failValidationErrors('RuleID is required');
            }

            $rule = $this->ruleDefModel->where('EndDate', null)->find((int) $id);
            if (!$rule) {
                return $this->respond([
                    'status' => 'failed',
                    'message' => 'Rule not found',
                    'data' => [],
                ], 404);
            }

            $linkedTests = $this->ruleDefModel->getLinkedTests((int) $id);

            $rule['linkedTests'] = $linkedTests;

            return $this->respond([
                'status' => 'success',
                'message' => 'fetch success',
                'data' => $rule,
            ], 200);
        } catch (\Throwable $e) {
            log_message('error', 'RuleController::show error: ' . $e->getMessage());
            return $this->respond([
                'status' => 'failed',
                'message' => 'Failed to fetch rule',
                'data' => [],
            ], 500);
        }
    }

    public function create()
    {
        $input = $this->requirePatchPayload($this->request->getJSON(true) ?? []);
        if ($input === null) {
            return;
        }

        $validation = service('validation');
        $validation->setRules([
            'RuleCode' => 'required|max_length[50]',
            'RuleName' => 'required|max_length[100]',
            'EventCode' => 'required|max_length[50]',
            'TestSiteIDs' => 'required',
            'TestSiteIDs.*' => 'is_natural_no_zero',
            'ConditionExpr' => 'permit_empty|max_length[1000]',
        ]);

        if (!$validation->run($input)) {
            return $this->failValidationErrors($validation->getErrors());
        }

        $testSiteIDs = $input['TestSiteIDs'] ?? [];
        if (!is_array($testSiteIDs) || empty($testSiteIDs)) {
            return $this->failValidationErrors(['TestSiteIDs' => 'At least one TestSiteID is required']);
        }

        // Validate all TestSiteIDs exist
        $testDef = new TestDefSiteModel();
        foreach ($testSiteIDs as $testSiteID) {
            $exists = $testDef->where('EndDate', null)->find((int) $testSiteID);
            if (!$exists) {
                return $this->failValidationErrors(['TestSiteIDs' => "TestSiteID {$testSiteID} not found"]);
            }
        }

        $db = \Config\Database::connect();
        $db->transStart();

        try {
            $ruleData = [
                'RuleCode' => $input['RuleCode'],
                'RuleName' => $input['RuleName'],
                'Description' => $input['Description'] ?? null,
                'EventCode' => $input['EventCode'],
                'ConditionExpr' => $input['ConditionExpr'] ?? null,
                'ConditionExprCompiled' => $input['ConditionExprCompiled'] ?? null,
            ];

            $ruleID = $this->ruleDefModel->insert($ruleData, true);
            if (!$ruleID) {
                throw new \Exception('Failed to create rule');
            }

            // Link rule to test sites
            foreach ($testSiteIDs as $testSiteID) {
                $this->ruleDefModel->linkTest($ruleID, (int) $testSiteID);
            }

            $db->transComplete();
            if ($db->transStatus() === false) {
                throw new \Exception('Transaction failed');
            }

            return $this->respondCreated([
                'status' => 'success',
                'message' => 'Rule created successfully',
                'data' => ['RuleID' => $ruleID],
            ], 201);
        } catch (\Throwable $e) {
            $db->transRollback();
            log_message('error', 'RuleController::create error: ' . $e->getMessage());
            return $this->failServerError('Something went wrong: ' . $e->getMessage());
        }
    }

    public function update($id = null)
    {
        $input = $this->request->getJSON(true) ?? [];

        if (!$id || !is_numeric($id)) {
            $id = $input['RuleID'] ?? null;
        }
        if (!$id || !is_numeric($id)) {
            return $this->failValidationErrors('RuleID is required');
        }

        $existing = $this->ruleDefModel->where('EndDate', null)->find((int) $id);
        if (!$existing) {
            return $this->respond([
                'status' => 'failed',
                'message' => 'Rule not found',
                'data' => [],
            ], 404);
        }

        $validation = service('validation');
        $validation->setRules([
            'RuleCode' => 'permit_empty|max_length[50]',
            'RuleName' => 'permit_empty|max_length[100]',
            'EventCode' => 'permit_empty|max_length[50]',
            'TestSiteIDs' => 'permit_empty',
            'TestSiteIDs.*' => 'is_natural_no_zero',
            'ConditionExpr' => 'permit_empty|max_length[1000]',
        ]);

        if (!$validation->run($input)) {
            return $this->failValidationErrors($validation->getErrors());
        }

        $db = \Config\Database::connect();
        $db->transStart();

        try {
            $updateData = [];
            foreach (['RuleCode', 'RuleName', 'Description', 'EventCode', 'ConditionExpr', 'ConditionExprCompiled'] as $field) {
                if (array_key_exists($field, $input)) {
                    $updateData[$field] = $input[$field];
                }
            }

            if (!empty($updateData)) {
                $this->ruleDefModel->update((int) $id, $updateData);
            }

            // Update test site mappings if provided
            if (isset($input['TestSiteIDs']) && is_array($input['TestSiteIDs'])) {
                $testSiteIDs = $input['TestSiteIDs'];
                
                // Validate all TestSiteIDs exist
                $testDef = new TestDefSiteModel();
                foreach ($testSiteIDs as $testSiteID) {
                    $exists = $testDef->where('EndDate', null)->find((int) $testSiteID);
                    if (!$exists) {
                        throw new \Exception("TestSiteID {$testSiteID} not found");
                    }
                }

                // Get current linked tests
                $currentLinks = $this->ruleDefModel->getLinkedTests((int) $id);
                
                // Unlink tests that are no longer in the list
                foreach ($currentLinks as $currentTestSiteID) {
                    if (!in_array($currentTestSiteID, $testSiteIDs)) {
                        $this->ruleDefModel->unlinkTest((int) $id, $currentTestSiteID);
                    }
                }
                
                // Link new tests
                foreach ($testSiteIDs as $testSiteID) {
                    if (!in_array($testSiteID, $currentLinks)) {
                        $this->ruleDefModel->linkTest((int) $id, (int) $testSiteID);
                    }
                }
            }

            $db->transComplete();
            if ($db->transStatus() === false) {
                throw new \Exception('Transaction failed');
            }

            return $this->respond([
                'status' => 'success',
                'message' => 'Rule updated successfully',
                'data' => ['RuleID' => (int) $id],
            ], 200);
        } catch (\Throwable $e) {
            $db->transRollback();
            log_message('error', 'RuleController::update error: ' . $e->getMessage());
            return $this->failServerError('Something went wrong: ' . $e->getMessage());
        }
    }

    public function delete($id = null)
    {
        try {
            if (!$id || !is_numeric($id)) {
                return $this->failValidationErrors('RuleID is required');
            }

            $existing = $this->ruleDefModel->where('EndDate', null)->find((int) $id);
            if (!$existing) {
                return $this->respond([
                    'status' => 'failed',
                    'message' => 'Rule not found',
                    'data' => [],
                ], 404);
            }

            $this->ruleDefModel->delete((int) $id);

            return $this->respondDeleted([
                'status' => 'success',
                'message' => 'Rule deleted successfully',
                'data' => ['RuleID' => (int) $id],
            ]);
        } catch (\Throwable $e) {
            log_message('error', 'RuleController::delete error: ' . $e->getMessage());
            return $this->failServerError('Something went wrong: ' . $e->getMessage());
        }
    }

    public function validateExpr()
    {
        $input = $this->request->getJSON(true) ?? [];
        $expr = $input['expr'] ?? '';
        $context = $input['context'] ?? [];

        if (!is_string($expr) || trim($expr) === '') {
            return $this->failValidationErrors(['expr' => 'expr is required']);
        }
        if (!is_array($context)) {
            return $this->failValidationErrors(['context' => 'context must be an object']);
        }

        try {
            $svc = new RuleExpressionService();
            $result = $svc->evaluate($expr, $context);

            return $this->respond([
                'status' => 'success',
                'data' => [
                    'valid' => true,
                    'result' => $result,
                ],
            ], 200);
        } catch (\Throwable $e) {
            return $this->respond([
                'status' => 'failed',
                'data' => [
                    'valid' => false,
                    'error' => $e->getMessage(),
                ],
            ], 200);
        }
    }

    /**
     * Compile DSL expression to engine-compatible structure.
     * Frontend calls this when user clicks "Compile" button.
     */
    public function compile()
    {
        $input = $this->request->getJSON(true) ?? [];
        $expr = $input['expr'] ?? '';

        if (!is_string($expr) || trim($expr) === '') {
            return $this->failValidationErrors(['expr' => 'Expression is required']);
        }

        try {
            $svc = new RuleExpressionService();
            $compiled = $svc->compile($expr);

            return $this->respond([
                'status' => 'success',
                'data' => [
                    'raw' => $expr,
                    'compiled' => $compiled,
                    'conditionExprCompiled' => json_encode($compiled),
                ],
            ], 200);
        } catch (\Throwable $e) {
            return $this->respond([
                'status' => 'failed',
                'message' => 'Compilation failed',
                'data' => [
                    'error' => $e->getMessage(),
                ],
            ], 400);
        }
    }
}
