<?php

namespace classes;

require_once WD . '/modules/workfeed/workfeed_c.php';

use Exception;
use interfaces\workfeed_i;
use workfeed\config\workfeed_api_url_c;
use workfeed\workfeed_c;

class workfeed implements workfeed_i
{
    public workfeed_c $config;

    public function __construct()
    {
        $this->config = new workfeed_c();
    }

    /**
     * @throws Exception
     */
    public function requireModuleEnabled(): void
    {
        if (!$this->config->enabled->isTrue()) {
            throw new Exception('The workfeed module is not enabled.');
        }
    }

    /**
     * @throws Exception
     */
    public function listEmployees(array $filters = []): array|object
    {
        return $this->sendApiRequest('GET', '/employees');
    }

    /**
     * @throws Exception
     */
    public function getEmployee(string $id): object
    {
        $this->requireValidIdentifier($id, 'employee id');

        return $this->sendApiRequest('GET', '/employees/' . rawurlencode($id));
    }

    /**
     * @throws Exception
     */
    public function listShifts(array $filters = []): array|object
    {
        $normalizedFilters = $this->normalizeShiftFilters($filters);

        return $this->sendApiRequest('GET', '/shifts', $normalizedFilters);
    }

    /**
     * @throws Exception
     */
    public function getShift(string $id): object
    {
        $this->requireValidIdentifier($id, 'shift id');

        return $this->sendApiRequest('GET', '/shifts/' . rawurlencode($id));
    }

    /**
     * @throws Exception
     */
    public function listDepartments(array $filters = []): array|object
    {
        return $this->sendApiRequest('GET', '/departments');
    }

    /**
     * @throws Exception
     */
    private function sendApiRequest(string $method, string $path, array $query = []): array|object
    {
        $this->requireModuleEnabled();
        $this->requireConfiguredApiUrl();
        $this->requireConfiguredApiKey();
        $companyId = $this->requireConfiguredCompanyId();

        $url = $this->buildUrl(
            workfeed_api_url_c::normalizeApiUrlForValidation((string)$this->config->api_url->getVariableValue()),
            '/companies/' . rawurlencode($companyId) . '/' . ltrim($path, '/'),
            $query
        );
        $headers = [
            'Accept: application/json',
            'Authorization: ' . trim((string)$this->config->api_key->getVariableValue()),
        ];

        return $this->executeJsonRequest($method, $url, $headers);
    }

    /**
     * @throws Exception
     */
    private function executeJsonRequest(string $method, string $url, array $headers): array|object
    {
        $response = $this->executeRequest($method, $url, $headers);
        $decoded = json_decode($response['body']);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception('Invalid JSON response from Workfeed (HTTP ' . $response['status'] . ').');
        }

        if ($response['status'] >= 400) {
            throw new Exception($this->extractErrorMessage($decoded, $response['status'], 'Workfeed API request failed'));
        }

        if (is_object($decoded) || is_array($decoded)) {
            return $decoded;
        }

        return (object)[
            'value' => $decoded,
        ];
    }

    /**
     * @throws Exception
     */
    private function executeRequest(string $method, string $url, array $headers, ?string $body = null): array
    {
        $curl = curl_init();
        curl_setopt_array($curl, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_HTTPHEADER => $headers,
        ]);

        if ($body !== null) {
            curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
        }

        $responseBody = curl_exec($curl);
        $statusCode = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
        $error = curl_error($curl);
        curl_close($curl);

        if ($error !== '') {
            throw new Exception('cURL request to Workfeed failed: ' . $error);
        }

        if ($responseBody === false) {
            throw new Exception('Workfeed request returned an empty response.');
        }

        return [
            'status' => $statusCode,
            'body' => (string)$responseBody,
        ];
    }

    private function buildUrl(string $baseUrl, string $path = '', array $query = []): string
    {
        $url = rtrim(trim($baseUrl), '/');
        if ($path !== '') {
            $url .= '/' . ltrim($path, '/');
        }

        $query = array_filter($query, static function (mixed $value): bool {
            return $value !== null && $value !== '';
        });

        if ($query !== []) {
            $url .= '?' . http_build_query($query);
        }

        return $url;
    }

    /**
     * @throws Exception
     */
    private function requireConfiguredApiUrl(): void
    {
        $url = trim((string)$this->config->api_url->getVariableValue());
        if ($url === ''
            || filter_var(workfeed_api_url_c::normalizeApiUrlForValidation($url), FILTER_VALIDATE_URL) === false
            || !workfeed_api_url_c::isTrustedApiUrl($url)
        ) {
            throw new Exception('Invalid Workfeed API URL configured.');
        }
    }

    /**
     * @throws Exception
     */
    private function requireConfiguredApiKey(): void
    {
        if (trim((string)$this->config->api_key->getVariableValue()) === '') {
            throw new Exception('Invalid Workfeed API key configured.');
        }
    }

    /**
     * @throws Exception
     */
    private function requireConfiguredCompanyId(): string
    {
        $companyId = trim((string)$this->config->company_id->getVariableValue());
        if ($companyId === '') {
            throw new Exception('Invalid Workfeed CompanyID configured.');
        }

        return $companyId;
    }

    /**
     * @throws Exception
     */
    private function requireValidIdentifier(string $value, string $label): void
    {
        if (trim($value) === '') {
            throw new Exception('Invalid ' . $label . '.');
        }
    }

    private function filterAllowed(array $filters, array $allowedKeys): array
    {
        $allowed = array_flip($allowedKeys);
        $filtered = [];

        foreach ($filters as $key => $value) {
            if (isset($allowed[$key])) {
                $filtered[$key] = $value;
            }
        }

        return $filtered;
    }

    /**
     * @throws Exception
     */
    private function normalizeShiftFilters(array $filters): array
    {
        $filtered = $this->filterAllowed($filters, [
            'startFrom',
            'startTo',
            'from',
            'to',
            'employeeID',
            'employeeId',
            'released',
        ]);

        if (!isset($filtered['startFrom']) && isset($filtered['from'])) {
            $filtered['startFrom'] = $filtered['from'];
        }
        if (!isset($filtered['startTo']) && isset($filtered['to'])) {
            $filtered['startTo'] = $filtered['to'];
        }
        if (!isset($filtered['employeeID']) && isset($filtered['employeeId'])) {
            $filtered['employeeID'] = $filtered['employeeId'];
        }

        unset($filtered['from'], $filtered['to'], $filtered['employeeId']);

        if (!isset($filtered['startFrom']) || trim((string)$filtered['startFrom']) === '') {
            throw new Exception('Workfeed shift query requires startFrom.');
        }

        if (!isset($filtered['startTo']) || trim((string)$filtered['startTo']) === '') {
            throw new Exception('Workfeed shift query requires startTo.');
        }

        if (isset($filtered['released'])) {
            $filtered['released'] = $this->normalizeBooleanQueryValue($filtered['released']);
        }

        return $filtered;
    }

    private function normalizeBooleanQueryValue(mixed $value): string
    {
        if (is_bool($value)) {
            return $value ? 'true' : 'false';
        }

        if (is_string($value)) {
            $normalized = strtolower(trim($value));
            if (in_array($normalized, ['1', 'true', 'yes', 'on'], true)) {
                return 'true';
            }
            if (in_array($normalized, ['0', 'false', 'no', 'off'], true)) {
                return 'false';
            }
        }

        if (is_int($value)) {
            return $value === 1 ? 'true' : 'false';
        }

        return (string)$value;
    }

    private function extractErrorMessage(mixed $decoded, int $statusCode, string $fallback): string
    {
        if (is_object($decoded)) {
            if (isset($decoded->message) && is_string($decoded->message)) {
                return $decoded->message;
            }
            if (isset($decoded->error) && is_string($decoded->error)) {
                return $decoded->error;
            }
        }

        if (is_string($decoded) && trim($decoded) !== '') {
            return $decoded;
        }

        return $fallback . ' (HTTP ' . $statusCode . ').';
    }
}
