<?php
declare(strict_types=1);
use EnneoSDK\Api;
use EnneoSDK\ApiEnneo;
use EnneoSDK\Helpers;
use EnneoSDK\IntentInfo;
use EnneoSDK\IntentOption;
use EnneoSDK\Interaction;
require(getenv()['SDK'] ?? 'sdk.php');
/** @var stdClass $in */
######### Expected Input:
// contractId -> ID of the contract (required)
// readingDate -> date of the reading. Optional. If no date is provided, the ticket creation date will be used
// ticketDate -> date of ticket creation (required)
// value -> reading value (optional). If no value is provided, the function will check attachments and try OCR there
// ticketId -> ID of the ticket (required)
// channel -> ticket channel. Voice and chat can be handled synchronously.
// counter -> consumption-related counter. Value is empty while first execution.
// plausibilityReason -> reason to save invalid readings. Used only if value can't be saved without a reason
######### Possible Actions:
// For better readability, actions can be prefixed with "base_", if they are handled in main agent business logic
// save reading without reason
const BASE_SAVE_READING = 'base_save_reading';
// save invalid reading using plausibility reason
const BASE_SAVE_INVALID_READING = 'base_save_invalid_reading';
// reading date is not valid
const READING_DATE_INVALID = 'reading_date_invalid';
// no reading value provided
const READING_VALUE_NOT_PROVIDED = 'reading_value_not_provided';
// reading value is not valid
const READING_VALUE_INVALID = 'reading_value_invalid';
// success
const SAVE_READING_COMPLETED = 'save_reading_completed';
// Create an Interaction object for output and fetch contract data from ERP
$interaction = new Interaction($in);
$contract = ApiEnneo::getContract($in->contractId);
######### STEP: if "Save" or "Save unplausibel" action is already selected, try to save reading
if ($in->_action === BASE_SAVE_READING || $in->_action === BASE_SAVE_INVALID_READING) {
    try {
        $parameters = [
            'tenant' => 'tenant',
            'mockedStatus' => 'OK', // use PLAUSIBILITY_REASON_NEEDED to test unplausible reading
            'contractId' => $in->contractId,
            'readingDate' => $in->readingDate,
            'newConsumptions' => [
                [
                    'consumption' => $in->value,
                    'consumptionUnit' => 'KILOWATT_HOURS',
                    'consumptionRelatedCounter' => $in->counter
                ]
            ]
        ];
        if ($in->_acttion === BASE_SAVE_INVALID_READING) {
            if (!$in->_plausibilityReason) {
                $interaction->infos[] = new IntentInfo(
                    type: 'warning',
                    message: 'Bitte den Plausibilitätsgrund angeben'
                );
                $interaction->options[] = new IntentOption(
                    type: BASE_SAVE_INVALID_READING,
                    name: 'Erneut versuchen',
                    recommended: true
                );
                stopProcessing($interaction);
            }
            $parameters['params']['newConsumptions'][0]['plausibilityReason'] = $in->_plausibilityReason;
        }
        $response = Api::call(
            method: 'POST',
            url: 'https://echo.enneo.ai',
            params: $parameters
        );
    } catch (Throwable $exception) {
        $interaction->infos[] = new IntentInfo(
            type: 'danger',
            message: 'Zählerstand konnte nicht gespeichert werden'
        );
        $interaction->options[] = new IntentOption(
            type: BASE_SAVE_READING,
            name: 'Erneut versuchen',
            recommended: true
        );
        stopProcessing($interaction);
    }
    // On invalid readings: save using a plausibility reason or inform the customer
    if ($response->mockedStatus === 'PLAUSIBILITY_REASON_NEEDED') {
        $interaction->infos[] = new IntentInfo(
            type: 'warning',
            message: 'Zählerstand ist nicht plausibel. Bitte den Grund wür die Abweichung angeben.'
        );
        $interaction->options[] = new IntentOption(
            type: BASE_SAVE_INVALID_READING,
            name: 'Plausibilitätsgrund angeben und speichern',
            recommended: true
        );
        $interaction->options[] = new IntentOption(
            type: READING_VALUE_INVALID,
            name: 'Kunden über inplausiblen ZS informieren',
            recommended: false
        );
        stopProcessing($interaction);
    }
    // Success
    $interaction->infos[] = new IntentInfo(
        type: 'success',
        message: 'Zählerstand gespeichert'
    );
    $in->_action = SAVE_READING_COMPLETED;
    stopProcessing($interaction);
}
######### STEP: fetch existing readings
$meterReadingsDataResponse = Api::call(
    method: 'POST',
    url: 'https://echo.enneo.ai',
    params: [
        'contractId' => $in->contractId,
        'mockedResult' => getMockedMeterDataResponse()
    ]
);
$meterReadingsData = $meterReadingsDataResponse->mockedResult ?? getMockedMeterDataResponse();
// set consumption-related counter
$in->counter = $meterReadingsData->meters[0]->counterType;
######### STEP: get reading date and validate it
$in->readingDate = empty($in->readingDate) ? $in->ticketDate : $in->readingDate;
$isReadingDateValid = true;
// TODO:
// reading date can be validated here.
// On synchronous channel, user will be requested to change the date. On async channels, text template can be sent
$latestExistingReadingDate = max(array_map(fn($item) => $item->readingDate, $meterReadingsData->meters[0]->meterReadings));
if ($in->readingDate <= $latestExistingReadingDate) {
    $interaction->infos[] = new IntentInfo(
        type: 'warning',
        message: isSyncChannel($in)
            ? sprintf('Bitte geben Sie ein Datum nach dem %s ein', Helpers::formatDate($latestExistingReadingDate))
            : sprintf('Es liget bereits eine Ablseung vom %s vor', Helpers::formatDate($latestExistingReadingDate))
    );
    $interaction->options[] = new IntentOption(
        type: READING_DATE_INVALID,
        name: isSyncChannel($in) ? 'Datum korrigieren' : 'Kunden informieren',
        recommended: true
    );
    stopProcessing($interaction);
}
######### STEP: get reading value and validate it
if (!$in->value && !isSyncChannel($in)) {
    // on async channels (email), value could be provided as image in email attachment
    $in->value = getValueUsingOcrOnAttachments($in, $meterReadingsData);
}
if (!$in->value) {
    $interaction->infos[] = new IntentInfo(
        type: 'warning',
        message: 'Es wurde kein Zählerstand übermittelt'
    );
    $interaction->options[] = new IntentOption(
        type: READING_VALUE_NOT_PROVIDED,
        name: isSyncChannel($in) ? 'Zählerstand eingeben' : 'Kunden informieren',
        recommended: true
    );
    stopProcessing($interaction);
}
// TODO:
// reading value validation can be adjusted and improved here
$latestReadingValue = max(array_map(fn($item) => $item->consumption, $meterReadingsData->meters[0]->meterReadings));
if ($in->value <= $latestReadingValue) {
    $interaction->infos[] = new IntentInfo(
        type: 'warning',
        message: sprintf(
            'Zählerstand ist kleiner als zuletzt übermittelter Zählerstand (%s, %s)',
            Helpers::formatDate($latestExistingReadingDate),
            $latestReadingValue
        )
    );
    $interaction->options[] = new IntentOption(
        type: READING_VALUE_INVALID,
        name: isSyncChannel($in) ? 'Anderen Zählerstand eingeben' : 'Kunden informieren',
        recommended: true
    );
    stopProcessing($interaction);
}
######### STEP: data validation passed. Add "save" action
$interaction->options[] = new IntentOption(
    type: BASE_SAVE_READING,
    name: 'Zählerstand speichern',
    recommended: true
);
stopProcessing($interaction);
######### HELPER FUNCTIONS #########
function stopProcessing(Interaction $interaction): void
{
    echo json_encode($interaction);
    exit();
}
function isSyncChannel($in): bool
{
    return in_array($in->channel, ['chat', 'phone']);
}
function getValueUsingOcrOnAttachments(stdClass $in, stdClass $meterReadingsData): ?float
{
    // Last reading date & value can be provided to improve the OCR
    $latestReadingValue = max(array_map(fn($item) => $item->consumption, $meterReadingsData->meters[0]->meterReadings));
    $latestReadingDate = max(array_map(fn($item) => $item->readingDate, $meterReadingsData->meters[0]->meterReadings));
    $ticket = ApiEnneo::getTicket($in->ticketId);
    foreach ($ticket->attachments as $attachment) {
        if ($attachment->size < 10000) {
            // attachment to small (logo etc)
            continue;
        }
        if (!in_array($attachment->fileEnding, ['jpg', 'jpeg', 'png', 'gif'])) {
            // unsupported file extension
            continue;
        }
        $response = ApiEnneo::post(
            endpoint: '/api/cortex/ocrMeter',
            body: [
                'ticketId' => $in->ticketId,
                'fileUrl' => $_ENV['ENNEO_API_URL'] . $attachment->url,
                'readingDate' => $in->readingDate,
                'lastReading' => $latestReadingValue,
                'lastReadingDate' => $latestReadingDate,
            ]
        );
        if ($response->responses ?? $response->response) {
            $body = $response->responses ?? $response->response;
            // TODO: value2 can be used for HT/NT implementation
            $value2 = (float)($body->meter_reading_2 ?? $body->meterReading2);
            return (float)($body->meter_reading_1 ?? $body->meterReading1);
        }
    }
    return null;
}
/**
 * Generates a mocked response for meter data including meter details,
 * reading details, and associated properties.
 */
function getMockedMeterDataResponse(): array
{
    return [
        'meters' => [
            [
                'meterNumber' => '000000000000123456',
                'counterType' => 'ET',
                'numberOfPreDecimalDigitsForMeter' => 6,
                'numberOfPostDecimalDigitsForMeter' => 1,
                'addMeterReadingPossible' => true,
                'meterReadings' => [
                    [
                        'consumptionRelatedCounter' => 'ET',
                        'readingDate' => '2024-05-22',
                        'consumption' => 80048,
                        'consumptionUnit' => 'KILOWATT_HOURS',
                        'reason' => 'INITIAL_METER_COUNT'
                    ],
                    [
                        'consumptionRelatedCounter' => 'ET',
                        'readingDate' => '2024-12-21',
                        'consumption' => 81246,
                        'consumptionUnit' => 'KILOWATT_HOURS',
                        'reason' => 'INTERMEDIATE_READING'
                    ]
                ]
            ]
        ]
    ];
}