<?php

namespace classes;

require_once WD . '/interfaces/shelly_transport_i.php';

use Exception;
use interfaces\shelly_transport_i;

class gateway_shelly_transport implements shelly_transport_i
{
    public function __construct(
        private readonly ?edge_gateway_manager $manager = null,
        private readonly bool $localOnly = false
    )
    {
    }

    public function requireModuleEnabled(): void
    {
        // Gateway mode is department-scoped and does not depend on the Shelly cloud module flag.
    }

    public function requireValidSecretKey(): void
    {
        // Gateway mode does not use the Shelly cloud auth key.
    }

    /**
     * @throws Exception
     */
    public function sendPostRequest(string $endpoint, array $data, ?int $department_id = null): array|object|null
    {
        if ($department_id === null || $department_id <= 0) {
            throw new Exception('A department_id is required for gateway Shelly transport');
        }

        return match ($endpoint) {
            '/v2/devices/api/get' => $this->handleGetStates($department_id, $data),
            '/v2/devices/api/set/switch' => $this->handleSetSwitch($department_id, $data),
            '/v2/devices/api/batch/get' => $this->handleBatchGetStates($department_id, $data),
            '/v2/devices/api/batch/set/switch' => $this->handleBatchSetSwitch($department_id, $data),
            default => throw new Exception('Unsupported gateway Shelly transport endpoint: ' . $endpoint),
        };
    }

    /**
     * @return array<int,array<string,mixed>>
     * @throws Exception
     */
    private function handleGetStates(int $departmentId, array $data): array
    {
        $ids = array_values(array_filter(
            array_map(static fn(mixed $value): string => trim((string)$value), (array)($data['ids'] ?? [])),
            static fn(string $value): bool => $value !== ''
        ));

        $result = [];
        foreach ($ids as $logicalRelayId) {
            $status = $this->localOnly
                ? $this->manager()->dispatchRelayStatusLocalOnly($departmentId, $logicalRelayId)
                : $this->manager()->dispatchRelayStatus($departmentId, $logicalRelayId);
            $result[] = $this->normalizeRelayPayload($logicalRelayId, $status);
        }

        return $result;
    }

    /**
     * @throws Exception
     */
    private function handleSetSwitch(int $departmentId, array $data): array
    {
        $logicalRelayId = trim((string)($data['id'] ?? ''));
        if ($logicalRelayId === '') {
            throw new Exception('Shelly gateway switch requests require an id');
        }

        $toggleAfter = $this->normalizeToggleAfter(
            $data['toggle_after'] ?? $data['toggleAfter'] ?? $data['timer'] ?? null
        );

        $status = $this->localOnly
            ? $this->manager()->dispatchRelaySwitchLocalOnlyWithTimer(
                $departmentId,
                $logicalRelayId,
                (bool)($data['on'] ?? false),
                $toggleAfter
            )
            : $this->manager()->dispatchRelaySwitchWithTimer(
                $departmentId,
                $logicalRelayId,
                (bool)($data['on'] ?? false),
                $toggleAfter
            );

        return [$this->normalizeRelayPayload($logicalRelayId, $status)];
    }

    /**
     * @throws Exception
     */
    private function handleBatchGetStates(int $departmentId, array $data): array
    {
        $requests = [];
        foreach ((array)($data['commands'] ?? $data['targets'] ?? []) as $entry) {
            $command = is_array($entry) ? $entry : ['relay_id' => $entry];
            $relayId = trim((string)($command['relay_id'] ?? $command['relayId'] ?? $command['id'] ?? ''));
            if ($relayId === '') {
                continue;
            }
            $requests[] = [
                'target' => strtoupper(trim((string)($command['target'] ?? $relayId))),
                'relay_id' => $relayId,
            ];
        }

        return $this->manager()->queueRelayStatusBatch($departmentId, $requests, null, $this->localOnly);
    }

    /**
     * @throws Exception
     */
    private function handleBatchSetSwitch(int $departmentId, array $data): array
    {
        $requests = [];
        foreach ((array)($data['commands'] ?? []) as $entry) {
            if (!is_array($entry)) {
                continue;
            }
            $relayId = trim((string)($entry['relay_id'] ?? $entry['relayId'] ?? $entry['id'] ?? ''));
            if ($relayId === '') {
                continue;
            }
            $requests[] = [
                'target' => strtoupper(trim((string)($entry['target'] ?? $relayId))),
                'relay_id' => $relayId,
                'on' => (bool)($entry['on'] ?? false),
                'toggle_after' => $entry['toggle_after'] ?? $entry['toggleAfter'] ?? $entry['timer'] ?? null,
            ];
        }

        return $this->manager()->queueRelaySwitchBatch($departmentId, $requests, null, $this->localOnly);
    }

    /**
     * @param array<string,mixed> $status
     * @return array<string,mixed>
     */
    private function normalizeRelayPayload(string $logicalRelayId, array $status): array
    {
        $on = (bool)($status['on'] ?? $status['output'] ?? false);
        $online = (bool)($status['online'] ?? true);

        return [
            'id' => $logicalRelayId,
            'relay_id' => $logicalRelayId,
            'online' => $online,
            'on' => $on,
            'status' => [
                'switch:0' => [
                    'output' => $on,
                ],
            ],
            'binding' => (array)($status['binding'] ?? []),
            'execution' => (array)($status['execution'] ?? []),
            'raw' => (array)($status['raw'] ?? []),
        ];
    }

    private function manager(): edge_gateway_manager
    {
        return $this->manager ?? new edge_gateway_manager();
    }

    private function normalizeToggleAfter(mixed $toggleAfter): ?int
    {
        if ($toggleAfter === null || $toggleAfter === '') {
            return null;
        }

        $seconds = (int)$toggleAfter;
        return $seconds > 0 ? $seconds : null;
    }
}
