#!/usr/bin/env php
<?php
declare(strict_types=1);

require dirname(__DIR__) . '/bootstrap.php';

use Solportalen\Database\Connection;
use Solportalen\Application\DeviceWorker;
use Solportalen\Config\Env;
use Solportalen\Device\Growatt\GrowattSphReader;
use Solportalen\Device\Growatt\GrowattSphCommissioning;
use Solportalen\Device\Growatt\GrowattSphControl;
use Solportalen\Device\Profile\ProfileValidator;
use Solportalen\Device\Serial\LinuxSerialTransport;
use Solportalen\Device\Simulator\EnergySimulator;
use Solportalen\Repository\StateRepository;
use Solportalen\Integration\Forecast\ExternalDataService;
use Solportalen\Integration\Supplier\ElprisSupplierService;
use Solportalen\Energy\Planning\PlanService;
use Solportalen\Integration\Ocpp\OcppServer;
use Solportalen\Integration\Watts\WattsApiClient;
use Solportalen\Integration\Watts\WattsMqttWorker;
use Solportalen\Repository\HistoryRepository;
use Solportalen\Energy\Planning\AutomationSettings;
use Solportalen\Energy\Planning\AutomaticPlanService;

$command = $argv[1] ?? 'help';
if (in_array('--help', $argv, true) || $command === 'help') {
    echo "Solportalen CLI\n\napp:key database:migrate database:status user:create-admin demo:seed worker:device worker:watts-mqtt scheduler:run supplier:refresh watts:test serial:list serial:test modbus:read modbus:dump modbus:holding commissioning:mode-test commissioning:load-first commissioning:restore-snapshot profile:list profile:validate profile:import optimizer:run telemetry:aggregate retention:run backup:create health simulator:start test\n";
    exit(0);
}
try {
    switch ($command) {
        case 'cloud:pair':
            echo "Parringskode fra solpanel.linder.dk: ";
            $code=trim((string)fgets(STDIN));
            (new Solportalen\Integration\Cloud\CloudAgent(Connection::get()))->pair($code);
            echo "Pi’en er forbundet. Fjernstyring er deaktiveret som standard.\n"; break;
        case 'cloud:control':
            if (!in_array($argv[2]??'',['enable','disable'],true)) throw new RuntimeException('Brug cloud:control enable eller disable.');
            Solportalen\Integration\Cloud\CloudAgent::control($argv[2]==='enable');
            echo "Lokal fjernstyringstilladelse er opdateret. WRITES_ENABLED ændres ikke.\n"; break;
        case 'cloud:unpair':
            $file=Solportalen\Integration\Cloud\CloudAgent::credentialsPath();
            if (is_file($file) && !unlink($file)) throw new RuntimeException('Kunne ikke fjerne enhedsnøglen.');
            echo "Den lokale enhedsnøgle er fjernet. Afkobl også anlægget på portalen. Aktiv fjernmode afsluttes af device-workeren.\n"; break;
        case 'worker:cloud': (new Solportalen\Integration\Cloud\CloudAgent(Connection::get()))->run(in_array('--once',$argv,true)); break;
        case 'app:key': echo 'APP_KEY=' . bin2hex(random_bytes(32)) . PHP_EOL; break;
        case 'database:migrate':
            $pdo = Connection::get();
            foreach (glob(SOLPORTAL_ROOT . '/database/migrations/*.sql') ?: [] as $file) {
                foreach (preg_split('/;\s*(?:\r?\n|$)/', (string) file_get_contents($file), -1, PREG_SPLIT_NO_EMPTY) ?: [] as $sql) {
                    $pdo->exec($sql);
                }
            }
            echo "Databasen er migreret.\n"; break;
        case 'database:status': Connection::get()->query('SELECT 1'); echo "Database: OK\n"; break;
        case 'profile:list': foreach (glob(SOLPORTAL_ROOT . '/config/register-profiles/*.json') ?: [] as $file) echo basename($file) . PHP_EOL; break;
        case 'profile:validate':
            $file = $argv[2] ?? '';
            $real = realpath($file);
            if ($real === false || !str_starts_with($real, realpath(SOLPORTAL_ROOT . '/config/register-profiles') ?: '')) throw new RuntimeException('Profilen skal ligge i config/register-profiles.');
            $profile = json_decode((string) file_get_contents($real), true, 64, JSON_THROW_ON_ERROR); (new ProfileValidator())->validate($profile); echo "Profil: OK\n"; break;
        case 'simulator:start': case 'demo:seed': echo json_encode((new EnergySimulator())->state(), JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR) . PHP_EOL; break;
        case 'health':
            $state = (new StateRepository(Connection::get()))->current();
            $age = isset($state['received_timestamp']) ? time() - strtotime($state['received_timestamp']) : null;
            echo json_encode(['status'=>$age !== null && $age < 30 ? 'ok' : 'stale','mode'=>Env::get('DEVICE_MODE','growatt'),'data_age_seconds'=>$age,'writes_enabled'=>Env::bool('WRITES_ENABLED',false),'commissioning_writes_enabled'=>Env::bool('COMMISSIONING_WRITES_ENABLED',false),'time'=>gmdate(DATE_ATOM)], JSON_PRETTY_PRINT) . PHP_EOL; break;
        case 'test': require SOLPORTAL_ROOT . '/tests/run.php'; break;
        case 'serial:list': foreach (glob('/dev/serial/by-id/*') ?: glob('/dev/ttyUSB*') ?: [] as $device) echo $device . PHP_EOL; break;
        case 'worker:device': (new DeviceWorker())->run(in_array('--once', $argv, true)); break;
        case 'worker:watts-mqtt': (new WattsMqttWorker())->run(); break;
        case 'worker:ocpp': (new OcppServer(Connection::get()))->run(); break;
        case 'serial:test': case 'modbus:read':
            $transport = new LinuxSerialTransport(Env::get('SERIAL_DEVICE','/dev/ttyUSB0'), (int) Env::get('SERIAL_BAUD','9600'));
            $state = (new GrowattSphReader($transport, (int) Env::get('SERIAL_SLAVE_ID','1')))->readState();
            echo json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . PHP_EOL; break;
        case 'modbus:dump':
            $transport = new LinuxSerialTransport(Env::get('SERIAL_DEVICE','/dev/ttyUSB0'), (int) Env::get('SERIAL_BAUD','9600'));
            echo json_encode((new GrowattSphReader($transport, (int) Env::get('SERIAL_SLAVE_ID','1')))->rawSnapshot(), JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR) . PHP_EOL; break;
        case 'modbus:holding':
            $transport = new LinuxSerialTransport(Env::get('SERIAL_DEVICE','/dev/ttyUSB0'), (int) Env::get('SERIAL_BAUD','9600'));
            $snapshot=(new GrowattSphCommissioning($transport,(int)Env::get('SERIAL_SLAVE_ID','1')))->inspect();
            $json=json_encode($snapshot,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR).PHP_EOL;
            if(in_array('--save',$argv,true)){
                $target=SOLPORTAL_ROOT.'/var/commissioning-growatt-holding.json';$temporary=$target.'.tmp';
                if(file_put_contents($temporary,$json,LOCK_EX)===false||!rename($temporary,$target))throw new RuntimeException('Commissioning-snapshot kunne ikke gemmes.');
            }
            echo $json;break;
        case 'commissioning:mode-test': case 'commissioning:load-first': case 'commissioning:restore-snapshot':
            if(!Env::bool('WRITES_ENABLED')||!Env::bool('COMMISSIONING_WRITES_ENABLED'))throw new RuntimeException('Både WRITES_ENABLED og COMMISSIONING_WRITES_ENABLED skal være true.');
            if(!in_array('--confirm=LOCAL_MODBUS_WRITE_TEST',$argv,true))throw new RuntimeException('Manglende eksplicit bekræftelse: --confirm=LOCAL_MODBUS_WRITE_TEST');
            $transport=new LinuxSerialTransport(Env::get('SERIAL_DEVICE','/dev/ttyUSB0'),(int)Env::get('SERIAL_BAUD','9600'));
            $control=new GrowattSphControl($transport,(int)Env::get('SERIAL_SLAVE_ID','1'),true);
            try{
                if($command==='commissioning:load-first')$result=$control->setLoadFirst();
                elseif($command==='commissioning:restore-snapshot'){$file=SOLPORTAL_ROOT.'/var/commissioning-growatt-holding.json';if(!is_file($file))throw new RuntimeException('Det gemte commissioning-snapshot findes ikke.');$snapshot=json_decode((string)file_get_contents($file),true,64,JSON_THROW_ON_ERROR);$result=$control->restoreSnapshot($snapshot['raw']??[]);}
                else$result=$control->testMode((string)($argv[2]??''),(int)($argv[3]??30));
            }
            catch(Throwable $writeError){$audit=Connection::get()->prepare('INSERT INTO audit_log(actor,action,object_type,object_id,before_json,after_json,reason,ip_address,correlation_id,created_at) VALUES("local-cli",?,"growatt",NULL,NULL,?,"Fejlet lokal Modbus commissioning",NULL,UUID(),UTC_TIMESTAMP(6))');$audit->execute([$command,json_encode(['status'=>'failed','error'=>$writeError->getMessage()],JSON_THROW_ON_ERROR)]);throw$writeError;}
            $audit=Connection::get()->prepare('INSERT INTO audit_log(actor,action,object_type,object_id,before_json,after_json,reason,ip_address,correlation_id,created_at) VALUES("local-cli",?,"growatt",NULL,NULL,?,"Verificeret lokal Modbus commissioning",NULL,UUID(),UTC_TIMESTAMP(6))');
            $audit->execute([$command,json_encode($result,JSON_THROW_ON_ERROR)]);
            echo json_encode($result,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR).PHP_EOL;break;
        case 'scheduler:run':
            $pdo=Connection::get();$plans=new PlanService($pdo);$settings=(new AutomationSettings($pdo))->get();$result=['expired_plans'=>$plans->expireApprovedPlans()];$result+=(new ExternalDataService($pdo))->refresh();try{$result['suppliers']=(new ElprisSupplierService($pdo))->refresh();}catch(Throwable $e){$result['suppliers']='error: '.$e->getMessage();}$result['plan_id']=$plans->generate((int)$settings['horizon_hours']);$result['automation']=(new AutomaticPlanService($pdo))->run($result['plan_id']);echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . PHP_EOL; break;
        case 'supplier:refresh':
            $service=new ElprisSupplierService(Connection::get());echo json_encode(['result'=>$service->refresh(true),'comparison'=>$service->comparison()],JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR).PHP_EOL;break;
        case 'watts:test':
            echo json_encode((new WattsApiClient())->probe(),JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR).PHP_EOL;break;
        case 'automation:run':
            $pdo=Connection::get();$settings=(new AutomationSettings($pdo))->get();
            $id=(new PlanService($pdo))->generate((int)$settings['horizon_hours']);
            echo json_encode((new AutomaticPlanService($pdo))->run($id,in_array('--refresh',$argv,true)),JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR).PHP_EOL;break;
        case 'optimizer:run':
            $service=new PlanService(Connection::get());$id=$service->generate((int)($argv[2]??48));echo json_encode(['plan_id'=>$id,'plan'=>$service->latest()],JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR).PHP_EOL;break;
        case 'telemetry:aggregate': echo json_encode(['rows'=>(new HistoryRepository(Connection::get()))->aggregate()],JSON_PRETTY_PRINT).PHP_EOL;break;
        case 'retention:run': echo json_encode((new HistoryRepository(Connection::get()))->retain(),JSON_PRETTY_PRINT).PHP_EOL;break;
        case 'worker:mqtt': case 'profile:import': case 'backup:create': case 'user:create-admin':
            throw new RuntimeException('Kommandoen kræver installeret database eller konkret konfiguration. Se --help og dokumentationen.');
        default: throw new RuntimeException('Ukendt kommando: ' . $command);
    }
} catch (Throwable $error) { fwrite(STDERR, json_encode(['error'=>$error->getMessage(),'command'=>$command], JSON_UNESCAPED_UNICODE) . PHP_EOL); exit(1); }
