<?php

/**
 * Exportní API serveru Sreality.
 *
 * @category   Dalten
 * @package    Export
 * @subpackage Api
 */
class Dalten_Export_Api_Sreality implements Dalten_Export_Api_ApiInterface, Dalten_Export_Api_LoggableApiInterface,
    Dalten_Export_Api_ProjectApiInterface, Dalten_Export_Api_ExportTranslationInterface,
    Dalten_Export_Api_TopListingInterface, Dalten_Export_Api_ListListingsInterface, Dalten_Export_Api_VideoStatusInterface
{
    /**
     * Konvertor hodnot.
     *
     * @var Dalten_Export_Sreality
     */
    protected $_export;

    /**
     * Nastavení.
     *
     * @var Serenity_Config_Config
     */
    protected $_config;

    /**
     * Backend.
     *
     * @var Dalten_Export_Api_Backend_Sreality
     */
    protected $_backend;

    /**
     * Případný převaděč/vylepšovač adres. Použije se pouze bude-li nastaven.
     *
     * @var Dalten_AddressConverter_Interface
     */
    protected $_optionalAdressConverter = null;

    /**
     * Fixní část variabilní části session ID.
     *
     * @var string|null
     */
	protected $_hashKey = null;

    /**
     * Poslední platné session ID.
     *
     * @var string|null
     */
	protected $_sessionId = null;

    /**
     * Status kód - inzerát byl přijat, ale změněné povinné položky nebyly uloženy.
     */
    const STATUS_CANNOT_MODIFY = 204;

    /**
     * Status kód - inzerát nebyl přijat, klient měnil hlavní parametry (typicky podtyp).
     */
    const STATUS_CANNOT_MODIFY_MAIN = 484;

    /**
     * Status kód - inzerát nebyl přijat, je navázán na neexportovaný projekt.
     */
    const STATUS_UNKNOWN_DEV_PROJECT = 491;

    /**
     * Konstruktor.
     *
     * @param Dalten_Export_Sreality             $export  Konvertor hodnot.
     * @param Serenity_Config_Config             $config  Nastavení.
     * @param Dalten_Export_Api_Backend_Sreality $backend Backend.
     */
    public function __construct(Dalten_Export_Sreality $export,
        Serenity_Config_Config $config,
        Dalten_Export_Api_Backend_Sreality $backend)
    {
        $this->_export = $export;
        $this->_config = $config;
        $this->_backend = $backend;
    }

    /**
     * Naváže spojení se vzdáleným serverem.
     *
     * @param string $login            Přihlašovací jméno.
     * @param string $password         Heslo.
     * @param string $softwareKey      Softwarový klíč.
     * @param array  $additionalParams Pole dalších parametrů.
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru.
     */
    public function openConnection($login, $password, $softwareKey = '', array $additionalParams = [])
    {
        $hash = $this->_backend->getHash($login);

        if (!isset($hash->status) || (isset($hash->status) && $hash->status != 200)) {
            return new Dalten_Export_Api_ServerResponse(
                false,
                isset($hash->status) ? $hash->status : 500,
                isset($hash->statusMessage) ? $hash->statusMessage : 'getHash failed.'
            );
        }

        if (!preg_match('/^[0-9a-f]{32}$/', $password)) {
            // heslo není v md5 tvaru, zashashujeme ho
            $password = md5($password);
        }

        $this->_sessionId = $hash->output->sessionId;
        $this->_hashKey = $password . (string) $softwareKey;

        $response = $this->_backend->login($this->_getSessionId(false));

        return new Dalten_Export_Api_ServerResponse(
            isset($response->status) && $response->status == 200,
            isset($response->status) ? $response->status : 500,
            isset($response->statusMessage) ? $response->statusMessage : 'No response from server'
        );
    }

    /**
     * Vypočítá nové session ID, které posléze vrátí.
     *
     * @return string Nové session ID.
     */
    protected function _getSessionId()
    {
        $fixedPart = substr($this->_sessionId, 0, 48);
        $varPart = md5($this->_sessionId . $this->_hashKey);
        $this->_sessionId = $fixedPart . $varPart;

        return $this->_sessionId;
    }

    /**
     * Vyexportuje uživatele.
     *
     * @param array $userData Data uživatele ve formátu iRest 1.
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru, jestli se export podařil nebo co selhalo.
     */
    public function addUser(array $userData)
    {
        $userId = $userData['id'];
        $convertedUser = $this->_convertUserData($userData);

        $response = $this->_backend->addSeller(
            $this->_getSessionId(),
            '',
            $userId,
            $convertedUser
        );

        return new Dalten_Export_Api_ServerResponse(
            $response->status == 200,
            $response->status,
            $this->_translateErrorMessage($response->status, $response->statusMessage),
            [
                'remoteId' => isset($response->sellerId) ? $response->sellerId : null,
                'statusMessage' => $response->statusMessage,
            ]
        );
    }

    /**
     * Vyexportuje nabídku.
     *
     * @param array $listingData      Data nabídky ve formátu iRest 1.
     * @param array $userData         Data uživatele (makléře, kterému nabídka patří) ve formátu iRest 1.
     * @param array $images           Pole fotografií nabídky ve formátu iRest 1.
     * @param array $additionalParams Pro tento export, nejsou přídavná data potřebná.
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru, jestli se export podařil nebo co selhalo.
     */
    public function addListing(
        array $listingData, array $userData, array $images = [], array $additionalParams = []
    )
    {
        $synchronizeVideos = isset($listingData['videos']);
        $videos = isset($listingData['videos']) ? $listingData['videos'] : [];

        $listingData = $this->_convertListingData($listingData, $additionalParams);

        if (!$this->_checkReadyDate($listingData)) {
            return new Dalten_Export_Api_ServerResponse(
                false,
                500,
                'U pronájmů je nutné uvádět datum k nastěhování (nikoli text ihned aj.).'
            );
        }

        if (!$this->_checkPersonal($listingData)) {
            return new Dalten_Export_Api_ServerResponse(
                false,
                500,
                'U nabídek v družstevním vlastnictví musí být vyplněna položka Převod do OV.'
            );
        }

        if (!empty($userData)) {
            if (empty($userData['id'])) {
                $userData['id'] = sprintf('%u', crc32(serialize($userData['sreality_login'])));
            }

            $response = $this->addUser($userData);
            if (!$response->wasSuccessful()) {
                return $response;
            }

            $listingData['seller_rkid'] = (string) $userData['id'];
        }

        $warningNote = null;

        $response = $this->_backend->addAdvert($this->_getSessionId(), $listingData);
        $remoteId = isset($response->output->advertId) ? $response->output->advertId : null;
        if ($response->status != 200) {
            if ($response->status < 300) {
                $warningNote = $response->statusMessage;
                if ($response->status == 202) {
                    $warningNote = 'UIR/RUIAN se nepodařilo dohledat.';
                }
                if ($response->status == 203) {
                    $warningNote = 'Adresa není jednoznačně zadaná. Upřesněte adresu.';
                }
            } elseif ($response->status == 491) {
                return new Dalten_Export_Api_ServerResponse(
                    false,
                    $response->status,
                    'Projektu nabídky vypršela platnost. Před exportem této nabídky projekt přeexportujte nebo stáhněte.'
                );
            } else {
                return new Dalten_Export_Api_ServerResponse(
                    false,
                    $response->status,
                    $this->_translateErrorMessage($response->status, $response->statusMessage),
                    ['statusMessage' => $response->statusMessage]
                );
            }
        }

        $remotePhotos = $this->_backend->listPhoto($this->_getSessionId(), null, $listingData['advert_rkid']);
        if ($remotePhotos['output'] instanceof Dalten_Data_ArrayObject) {
            $remotePhotos = $remotePhotos['output']->toArray(true);
        } else {
            $remotePhotos = [];
        }

        if ($this->_comparePhotos($images, $remotePhotos) === false || !empty($additionalParams['reexport_photos'])) {
            $this->_deleteImages($listingData['advert_rkid']);
            $response = $this->_addImages($listingData['advert_rkid'], $images);
            if (!$response->wasSuccessful()) {
                return $response;
            }
        }

        $videoSyncInfo = false;
        if ($synchronizeVideos) {
            $videoSyncInfo = $this->_synchronizeVideos($listingData['advert_rkid'], $videos);
        }

        $response = new Dalten_Export_Api_ServerResponse(
            true,
            200,
            ((!empty($warningNote)) ? $warningNote : ''),
            ['remoteId' => $remoteId]
        );

        $response->setData(['remoteId' => $remoteId]);
        if ($videoSyncInfo) {
            // video bylo změněno, poznačíme prověření exportu
            $response->setData(['remoteId' => $remoteId, 'videoStatus'=>$videoSyncInfo]);
        }

        return $response;
    }

    /**
     * Odstraní nabídku ze vzdáleného serveru.
     *
     * @param array $listingData      Data nabídky ve formátu iRest 1.
     * @param array $additionalParams Specifická data pro daný export (zde prázdné pole).
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru.
     */
    public function deleteListing(array $listingData, array $additionalParams = [])
    {
        $response = $this->_backend->delAdvert(
            $this->_getSessionId(),
            null,
            isset($listingData['kod']) ? $listingData['kod'] : null
        );

        return new Dalten_Export_Api_ServerResponse(
            $response->status == 200,
            $response->status,
            $response->statusMessage
        );
    }

    /**
     * Vytopuje nemovitost.
     *
     * @param array $listingData      Data nemovitosti.
     * @param array $additionalParams Další parametry (nepoužívá se).
     *
     * @return Dalten_Export_Api_ServerResponseVerifiable Odpověď serveru.
     */
    public function topListing(array $listingData, array $additionalParams = [])
    {
        $response = $this->_backend->topAdvert(
            $this->_getSessionId(),
            null,
            isset($listingData['kod']) ? $listingData['kod'] : null
        );

        return new Dalten_Export_Api_ServerResponse(
            $response->status == 200,
            $response->status,
            $response->statusMessage
        );
    }

    /**
     * Odstraní uživatele ze serveru.
     *
     * @param array $userData Data uživatele ve formátu iRest 1.
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru.
     */
    public function deleteUser(array $userData)
    {
        $response = $this->_backend->delSeller(
            $this->_getSessionId(),
            null,
            isset($userData['id']) ? $userData['id'] : null
        );

        return new Dalten_Export_Api_ServerResponse(
            $response->status == 200,
            $response->status,
            $response->statusMessage
        );
    }

    /**
     * Získá výpis všech inzerátů kanceláře.
     *
     * Seznam inzerátů je ve formátu, v jakém je vrácen ze serveru.
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru.
     */
    public function getListingsList()
    {
        $response = $this->_backend->listAdvert($this->_getSessionId());

        return new Dalten_Export_Api_ServerResponse(
            $response->status == 200,
            $response->status,
            $response->statusMessage,
            $response->output
        );
    }

    /**
     * Vždy uspěje.
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru.
     */
    public function closeConnection()
    {
        return new Dalten_Export_Api_ServerResponse(true);
    }

    /**
     * Vrací seznam uživatelů.
     *
     * Seznam uživatelů vracíme tak jak jsme ho dostali ze serveru.
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru.
     */
    public function listUsers()
    {
        $response = $this->_backend->listSeller(
            $this->_getSessionId()
        );

        return new Dalten_Export_Api_ServerResponse(
            $response->status == 200,
            $response->status,
            $response->statusMessage,
            $response->output
        );
    }

    /**
     * Vrátí statistiky. Pokud jsou $advertId a $advertRkId prázdná vrací statistiky pro všechny inzeráty.
     *
     * Statistiky prozatím vracíme v syrové formě tak jak je dostáváme.
     *
     * @param array $advertId   Pole ID nabídek.
     * @param array $advertRkId Pole vlastních ID nabídek.
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru.
     */
    public function listStat(array $advertId, array $advertRkId)
    {
        $response = $this->_backend->listStat(
            $this->_getSessionId(),
            $advertId,
            $advertRkId
        );

        return new Dalten_Export_Api_ServerResponse(
            $response->status == 200,
            $response->status,
            $response->statusMessage,
            $response->output
        );
    }

    /**
     * Vrací denní statistiky.
     *
     * Statistiky prozatím vracíme v syrové formě.
     *
     * @param int    $advertId   ID nabídky.
     * @param string $advertRkId Vlastní ID nabídky.
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru.
     */
    public function listDailyStat($advertId, $advertRkId)
    {
        $response = $this->_backend->listDailyStat(
            $this->_getSessionId(),
            $advertId,
            $advertRkId
        );

        return new Dalten_Export_Api_ServerResponse(
            $response->status == 200,
            $response->status,
            $response->statusMessage,
            $response->output
        );
    }

    /**
     * Výpis statistiky všech inzerátů daného klienta za konkrétní den určený parametrem date.
     *
     * @param string $date Datum, kterého se statistika týká.
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru.
     */
    public function listAllDailyStat($date)
    {
        $response = $this->_backend->listAllDailyStat(
            $this->_getSessionId(),
            $date
        );

        return new Dalten_Export_Api_ServerResponse(
            $response->status == 200,
            $response->status,
            $response->statusMessage,
            $response->output
        );
    }

    /**
     * Výpis celých zpráv, ze všech inzerátu, odeslaných na RK v zadané datum.
     *
     * @param string $date Datum, kterého se statistika týká.
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru.
     */
    public function listFullInquiry($date)
    {
        $response = $this->_backend->listFullInquiry(
            $this->_getSessionId(),
            $date
        );

        return new Dalten_Export_Api_ServerResponse(
            $response->status == 200,
            $response->status,
            $response->statusMessage,
            $response->output
        );
    }

    /**
     * Nastaví logger pro backend.
     *
     * Logger bude použit pouze pokud to backend dovoluje.
     *
     * @param Dalten_Export_Api_Backend_Logger_LoggerInterface $logger Instance loggeru.
     *
     * @return bool Podařilo se přiřadit backendu logger?
     */
    public function setLogger(Dalten_Export_Api_Backend_Logger_LoggerInterface $logger)
    {
        if ($this->_backend instanceof Dalten_Export_Api_Backend_LoggableBackendInterface) {
            $this->_backend->setLogger($logger);

            return true;
        }

        return false;
    }

    /**
     * Odstraní nastavený logger pro backend.
     *
     * @return Dalten_Export_Api_Sreality Fluent interface.
     */
    public function removeLogger()
    {
        if ($this->_backend instanceof Dalten_Export_Api_Backend_LoggableBackendInterface) {
            $this->_backend->removeLogger();
        }

        return $this;
    }

    /**
     * Porovná obrázky, přiložené k fotce, s obrázky, které jsou již nahrány na serveru srealit.
     *
     * @param array $listingImages Pole informací o fotkách nabídky ve formátu iRest1.
     * @param array $remoteImages  Pole informací o fotkách nabídky ve formátu výsledku metody listPhoto.
     *
     * @return bool Jsou fotky stejné?
     */
    protected function _comparePhotos(array $listingImages, array $remoteImages)
    {
        if (count($listingImages) != count($remoteImages)) {
            return false;
        }

        $listingImages = array_values($listingImages);

        $indexedListingImages = [];
        foreach ($listingImages as $listingImage) {
            if (isset($listingImage['poradi'])) {
                $indexedListingImages[$listingImage['poradi']] = $listingImage;
            } else {
                // pokud někde chybí pořadí, nebudeme podle něj řadit :)
                $indexedListingImages = $listingImages;
                break;
            }
        }
        ksort($indexedListingImages);
        $listingImages = array_values($indexedListingImages);
        $listingImages = array_slice($listingImages, 0, 30);
        $remoteImages = array_values($remoteImages);

        foreach ($listingImages as $index => $image) {
            if (empty($remoteImages[$index])) {
                return false;
            }
            if ($image['id'] != $remoteImages[$index]['photo_rkid']) {
                return false;
            }
        }

        return true;
    }

    /**
     * Odstraní existující fotografie dané nabídky ze serveru.
     *
     * @param string $listingCode Kód nabídky.
     *
     * @return Dalten_Export_Api_ServerResponse Informace o výsledku smazání.
     */
    protected function _deleteImages($listingCode)
    {
        $success = true;
        $status = 200;
        $message = '';

        $remoteImages = $this->_backend->listPhoto($this->_getSessionId(), null, $listingCode)->toArray(true);

        if (isset($remoteImages['output']) && is_array($remoteImages['output'])) {
            foreach ($remoteImages['output'] as $imageData) {
                $response = $this->_backend->delPhoto($this->_getSessionId(), $imageData['photo_id'], null);
                if ($response->status != 200) {
                    $success = false;
                    $status = $response->status;
                    $message = $response->statusMessage;
                }
            }
        }

        return new Dalten_Export_Api_ServerResponse($success, $status, $message);
    }

    /**
     * Odstraní existující fotografie daného projektu ze serveru.
     *
     * @param string $projectRkId Kód projektu.
     *
     * @return Dalten_Export_Api_ServerResponse Informace o výsledku smazání.
     */
    private function _deleteImagesProject($projectRkId)
    {
        $success = true;
        $status = 200;
        $message = '';

        $remoteImages = $this->_backend->listProjectPhoto($this->_getSessionId(), null, $projectRkId)->toArray(true);

        if (isset($remoteImages['output']) && is_array($remoteImages['output'])) {
            foreach ($remoteImages['output'] as $imageData) {
                $response = $this->_backend->delProjectPhoto($this->_getSessionId(), $imageData['photo_id'], null);
                if ($response->status != 200) {
                    $success = false;
                    $status = $response->status;
                    $message = $response->statusMessage;
                }
            }
        }

        return new Dalten_Export_Api_ServerResponse($success, $status, $message);
    }

    /**
     * Vyexportuje fotografie nabídky.
     *
     * @param string $listingCode Kód nabídky ve formátu Nxxxxx.
     * @param array  $images      Pole fotografií ve formátu iRest 1.
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru, jestli se export podařil nebo co selhalo.
     */
    protected function _addImages($listingCode, array $images)
    {
        $success = true;
        $status = 200;
        $message = "";
        $images = array_values($images);
        foreach ($images as $i => $image) {
            $file = realpath($image['soubor']);
            if ($file) {
                $image = [
                    'data' => file_get_contents($file),
                    'main' => !$i,
                    'order' => $image['poradi'],
                    'alt' => $image['popis'],
                    'photo_rkid' => $image['id'],
                ];

                $response = $this->_backend->addPhoto(
                    $this->_getSessionId(),
                    null,
                    $listingCode,
                    $image
                );

                $status = $response->status;

                if ($status != 200) {
                    $success = false;
                    $message = $message . PHP_EOL . basename($file) . ' - ' . $this->_translatePhotoErrorMessage($response->status, $response->statusMessage);
                }

                if ($status == 407) {
                    return new Dalten_Export_Api_ServerResponseFatal(
                        false,
                        407,
                        sprintf('Selhal export %d. fotografie (photo_rkid=%d, soubor %s).', $image['order'], $image['photo_rkid'], basename($file))
                    );
                }
            }
        }

        if (empty($message)) {
            $message = 'OK';
        }

        return new Dalten_Export_Api_ServerResponse($success, $status, $message);
    }

    /**
     * Vyexportuje fotografie projektu.
     *
     * @param string $projectRkId Kód projektu.
     * @param array  $images      Pole fotografií ve formátu iRest 1.
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru, jestli se export podařil nebo co selhalo.
     */
    private function _addImagesProject($projectRkId, array $images)
    {
        $success = true;
        $status = 200;
        $message = "Fotografie: \n";
        // Omezíme počet fotek na maximálně 30.
        $images = array_slice($images, 0, 30);
        foreach (array_values($images) as $i => $image) {
            $file = realpath($image['soubor']);
            if ($file) {
                $image = [
                    'data' => file_get_contents($file),
                    'main' => !$i,
                    'order' => $image['poradi'],
                    'alt' => $image['popis'],
                    'photo_rkid' => $image['id'],
                ];

                $response = $this->_backend->addProjectPhoto(
                    $this->_getSessionId(),
                    0,
                    $projectRkId,
                    $image
                );

                $status = $response->status;
                $message .= basename($file) . ' - ' . $this->_translatePhotoErrorMessage($response->status, $response->statusMessage) . PHP_EOL;

                if ($status != 200) {
                    $success = false;
                }

                if ($status == 407) {
                    return new Dalten_Export_Api_ServerResponseFatal(
                        false,
                        407,
                        sprintf('Selhal export %d. fotografie (photo_rkid=%d, soubor %s).', $image['order'], $image['photo_rkid'], basename($file))
                    );
                }
            }
        }

        return new Dalten_Export_Api_ServerResponse($success, $status, $message);
    }

    /**
     * Vyexportuje projekt.
     *
     * @param array $projectData      Data projektu ve formátu iRest 1.
     * @param array $userData         Data uživatele (makléře, kterému projekt patří) ve formátu iRest 1.
     * @param array $listingIds       Pole s IDčky nabídek (ve tvaru id=>kod).
     * @param array $images           Pole fotografií projektu ve formátu iRest 1.
     * @param array $additionalParams Údaje které vrací server při exportu a jsou potřeba pro další
     *                                práci s nabídkou.
     *                                Data můžou sloužit pro editaci apod...
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru, jestli se export podařil nebo co selhalo.
     */
    public function addProject(array $projectData, array $userData, array $listingIds = [],
        array $images = [], array $additionalParams = [])
    {
        if ($this->_optionalAdressConverter) {
            $projectData = $this->_optionalAdressConverter->convertAddress($projectData);
        }

        $projectData = $this->_export->convertEntityValues('project', $projectData, $this->_config->project);

        if (!empty($userData)) {
            if (empty($userData['id'])) {
                $userData['id'] = sprintf('%u', crc32(serialize($userData['sreality_login'])));
            }

            $response = $this->addUser($userData);
            if (!$response->wasSuccessful()) {
                return $response;
            }

            $projectData['seller_rkid'] = (string) $userData['id'];
        }

        $response = $this->_backend->addProject($this->_getSessionId(), $projectData);
        $remoteId = isset($response->output->projectId) ? $response->output->projectId : null;

        $warningNote = null;

        if ($response->status != 200) {
            if ($response->status < 300) {
                $warningNote = $response->statusMessage;
            } else {
                return new Dalten_Export_Api_ServerResponse(
                    false,
                    $response->status,
                    $this->_translateErrorMessage($response->status, $response->statusMessage),
                    ['statusMessage' => $response->statusMessage]
                );
            }
        }

        $remotePhotos = $this->_backend->listProjectPhoto($this->_getSessionId(), 0, $projectData['project_rkid']);
        if ($remotePhotos['output'] instanceof Dalten_Data_ArrayObject) {
            $remotePhotos = $remotePhotos['output']->toArray();
        } else {
            $remotePhotos = [];
        }

        if ($this->_comparePhotos($images, $remotePhotos) === false || !empty($additionalParams['reexport_photos'])) {
            $this->_deleteImagesProject($projectData['project_rkid']);

            $response = $this->_addImagesProject($projectData['project_rkid'], $images);
            if (!$response->wasSuccessful()) {
                return $response;
            }
        }

        $response = new Dalten_Export_Api_ServerResponse(
            true,
            200,
            (!empty($warningNote) ? $warningNote : ''),
            ['remoteId' => $remoteId]
        );
        $response->setData([
            'remoteId' => $remoteId,
            'detailUrl' => $this->getProjectUrl($remoteId),
        ]);

        return $response;
    }

    /**
     * Sestaví URL projektu (kvůli přetížení).
     *
     * @param int $remoteId Vzdálené ID.
     * @return string
     */
    protected function getProjectUrl($remoteId)
    {
        return sprintf('https://www.sreality.cz/projekt-detail/nahled/%d', $remoteId);
    }

    /**
     * Odstraní projekt ze vzdáleného serveru.
     *
     * @param array $projectData      Data projektu ve formátu iRest 1.
     * @param array $additionalParams Specifická data pro daný export.
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru.
     */
    public function deleteProject(array $projectData, array $additionalParams = [])
    {
        $response = $this->_backend->delProject(
            $this->_getSessionId(),
            '',
            $projectData['kod']
        );

        return new Dalten_Export_Api_ServerResponse(
            $response->status == 200,
            $response->status,
            $response->statusMessage
        );
    }

    /**
     * Najde anglický překlad popisu.
     *
     * @param array $originalListingData Původní (nepřevedená) data nabídky, použije se položka translations.
     *
     * @return string Anglický překlad popisu nebo prázdný string, pokud popis není přeložen.
     */
    protected function _findDescriptionTranslation(array $originalListingData)
    {
        if (!empty($originalListingData['translations']) && is_array($originalListingData['translations'])) {
            $translations = $originalListingData['translations'];
            foreach ($translations as $translated) {
                if ($translated['zkratka'] == 'en' && !empty($translated['nemovitost_popis_detailni'])) {
                    if (!empty($translated['nemovitost_popis_zakladni'])) {
                        return $translated['nemovitost_popis_zakladni'] . ' ' . $translated['nemovitost_popis_detailni'];
                    }

                    return $translated['nemovitost_popis_detailni'];
                }
            }
        }

        return '';
    }

    /**
     * Najde anglický překlad poznámky k ceně.
     *
     * @param array $originalListingData Původní (nepřevedená) data nabídky, použije se položka translations.
     *
     * @return string Anglický překlad poznámky k ceně nebo prázdný string, pokud není přeložena.
     */
    protected function _findPriceNoteTranslation(array $originalListingData)
    {
        if (!empty($originalListingData['translations']) && is_array($originalListingData['translations'])) {
            $translations = $originalListingData['translations'];
            foreach ($translations as $translated) {
                if ($translated['zkratka'] == 'en' && !empty($translated['nemovitost_cena_poznamka'])) {
                    return $translated['nemovitost_cena_poznamka'];
                }
            }
        }

        return '';
    }

    /**
     * Přeloží chybové hlášky do lidsky čitelné podoby.
     *
     * @param int    $statusCode    Kód chybové hlášky.
     * @param string $statusMessage Původní chybová hláška z Sreality (jako fallback nebo doplnění detailů).
     *
     * @return string Přeložená chybová hláška.
     */
    protected function _translateErrorMessage($statusCode, $statusMessage)
    {
        $messages = [
            200 => 'Vše je v pořádku.',
            202 => 'Inzerát/projekt přijat s tím, že předaný RUIAN nebo UIR kód nebyl rozpoznán.',
            203 => 'Inzerát/projekt přijat s tím, že textová reprezentace lokality nabídla více jak jednu možnost.',
            204 => 'Inzerát přijat s tím, že modifikované povinné položky nebyly uloženy.',
            205 => 'Inzerát přijat s nepřesnou adresou s tím, že nemusí být zařazen v některých výpisech.',
            404 => 'Firma, makléř, inzerát, projekt či fotografie nebyla nalezena.',
            405 => 'Použitý SW klíc není aktivní.',
            407 => 'Přihlášení se nezdařilo, zkontrolujte heslo a SW klíč.',
            410 => 'Obrázek je příliš velký.',
            412 => 'Fotografie nemá dostatečné rozměry.',
            413 => 'Videozáznam je příliš velký',
            414 => 'Počet nahraných fotografií překrocil limit.',
            415 => 'Firma není aktivní.',
            420 => 'Behem překódovávání videozáznamu s ním nelze pracovat (po 1 hodině stav encoding vyprší)',
            450 => 'Fotografie je již vložená, toto id je svázáno s jiným inzerátem.',
            451 => 'Fotku nelze přidat, jelikož je duplicitní.',
            452 => 'Nejsou vyplněny všechny povinné položky nebo jsou špatného typu.', // tady ještě dáme zbytek hlášky
            453 => 'Selhala validace lokality.',
            454 => 'Selhalo nejen rozpoznání RUIAN nebo UIR kódu, ale i textová reprezentace.',
            455 => 'Text obsahuje nevhodná slova nebo je příliš krátký (dlouhý).',
            461 => 'ID makléře neexistuje.',
            462 => 'Login makléře je již použit.',
            463 => 'Login makléře nelze dohledat v databázi Seznamu (neregistrován).',
            464 => 'S Vaším stávajícím členstvím nelze přidat další inzerát.', // REALITY - TF
            465 => 'Nemáte dostatek T-coins pro topování inzerátu.', // REALITY - TF
            466 => 'Tento server přijímá jen inzeráty s měnou Kč.', // REALITY - TF
            471 => 'IČ developera nenalezeno',
            476 => 'Neznámý typ obrázku, používejte obrázky typu JPG.',
            477 => 'Nelze zvýhodnit (topovat) inzerát v den vložení.',
            478 => 'Nelze zvýhodnit (topovat) inzerát oznacený jako duplicitní.',
            479 => 'Nelze zvýhodnit (topovat) nezveřejnený nebo neschválený inzerát.',
            482 => 'Inzerát za aktuální den již byl zvýhodnen maximální počet krát.',
            483 => 'Povinné položky již není možné modifikovat protože uplynula doba 3 hodiny od prvního importu.',
            484 => 'Hlavní položky již není možné modifikovat protože uplynula doba 3 hodiny od prvního importu.',
            485 => 'Zadané RKID není jednoznačné.',
            500 => 'Interní chyba systému (nahodilý výskyt=timeout, pravidelný=bug).',
        ];
        $moreInfo = '';

        if ($statusCode == 452) {
            if (false !== strpos($statusMessage, '(client_is_employee=1)')) {
                $moreInfo = ' Makléř musí mít vyplněnou “Formu spolupráce” - HPP nebo IČ.
			        Nastavení proveďte v editaci uživatele.';
            } elseif (false !== strpos($statusMessage, 'advert_room_count')) {
                $moreInfo = ' Povinná položka “Počet pokojů” je povinná.';
            } elseif (false !== strpos($statusMessage, 'object_type')) {
                $moreInfo = ' “Typ domu” je pro tento typ nemovitosti povinný.';
            } elseif (false !== strpos($statusMessage, 'client_login')) {
                $moreInfo = ' Login makléře na Sreality.cz není vyplněn. Přejděte do úpravy profilu makléře a doplňte jej.';
            } elseif (false !== strpos($statusMessage, 'share_numerator')) {
                $moreInfo = ' Neplatný podíl - jmenovatel nesmí být menší než čitatel.';
            } else {
                $moreInfo = ' ' . $statusMessage;
            }

        }

        if ($statusCode == 455) {
            $items = [];
            preg_match('~item \'(.+)\'~', $statusMessage, $items);
            $moreInfo = ' (položky: ' . $items[1] . ')';
        }

        if ($statusCode == 404) {
            $moreInfo = ' - ' . $statusMessage;
        }

        return (isset($messages[$statusCode]) ? ($messages[$statusCode] . $moreInfo) : ('Neznámá chyba. Zkuste zakázku reexportovat. ' . PHP_EOL . $statusMessage));
    }

    /**
     * Přeloží chybové hlášky při přidání fotek..
     *
     * @param int    $statusCode    Kód chybové hlášky.
     * @param string $statusMessage Původní chybová hláška z Sreality (jako fallback nebo doplnění detailů).
     *
     * @return string Přeložená chybová hláška.
     */
    protected function _translatePhotoErrorMessage($statusCode, $statusMessage)
    {
        // chyba 477 je jinak než v globálním překladu, jinak je to stejně
        $messages = [
            477 => 'Problém s přečtením metadat obrázku.',
        ];

        return (isset($messages[$statusCode]) ? $messages[$statusCode] : $this->_translateErrorMessage($statusCode, $statusMessage));
    }

    /**
     * Nastaví nepovinný převodník/vylepšovač adres.
     *
     * @param Dalten_AddressConverter_Interface $addressConverter
     */
    public function setOptionalAddressConverter(Dalten_AddressConverter_Interface $addressConverter)
    {
        $this->_optionalAdressConverter = $addressConverter;
    }

    /**
     * Synchronizuje videa na Sreality - nahraje chybějící a smaže přebývající.
     *
     * @param string $advertRkid RKID nabídky.
     * @param array  $videos     Informace o nahraných videích z Irestu.
     *
     * @return false|string Informace o tom, že byla videa upravena nebo FALSE, pokud nic neupravíme.
     */
    protected function _synchronizeVideos($advertRkid, $videos)
    {
        // synchronizace videí
        $pridano = $smazano = 0;
        $zobrazujemeVideo = false;

        // vylistujeme stávající
        $videoStatus = $this->listVideos($advertRkid);
        $exportedVideos = $videoStatus->getOutput()->toArray();

        // vytvoříme seznam těch co chceme
        $chceme = [];
        foreach ($videos as $video) {
            $chceme[] = (string) $video['id'];
        }

        // projedeme stávající a smažeme ty co nechceme
        // poznačíme si přitom ty co máme
        $mame = [];
        foreach ($exportedVideos as $exportedVideo) {
            if (isset($exportedVideo['video_name'])) {
                if (in_array($exportedVideo['video_name'], $chceme)) {
                    $mame[] = (string) $exportedVideo['video_name'];
                    $zobrazujemeVideo = $exportedVideo['video_state'] == 1;
                } else {
                    $smazanoStatus = $this->removeVideoByVideoId($advertRkid, $exportedVideo['video_id']);
                    if ($smazanoStatus->wasSuccessful()) {
                        $smazano++;
                    }
                }
            }
        }

        // vyexportujeme ty co naopak chceme (ale ještě nemáme)
        foreach ($videos as $video) {
            if (!in_array($video['id'], $mame)) {
                $uploadStatus = $this->uploadVideo($advertRkid, $video['url'], $video['id']);
                if ($uploadStatus->wasSuccessful()) {
                    $pridano++;
                }
            }
        }

        if ($pridano > 0 || $smazano > 0) {
            // nějaké video jsme přidali nebo smazali, prověříme jak se nahrálo
            return 'in_progress';
        }

        if (count($mame)==1 && $zobrazujemeVideo) {
            // video už tam je a zobrazuje se, chceme rovnou zobrazit
            return 'visible';
        }

        return false;
    }

    /**
     * Vloží video k nabídce.
     *
     * @param string $advertRkId RK ID nabídky.
     * @param string $videoPath  Cesta k (dočasnému) souboru s videem.
     *
     * @return Dalten_Export_Api_ServerResponse Odpověď serveru.
     */
    public function uploadVideo($advertRkId, $videoUrl, $videoName)
    {
        $filename = pathinfo($videoPath, PATHINFO_FILENAME);
        $data = [
            'video_name' => (string) $videoName,
            'video_url' => $videoUrl,
        ];
        $response = $this->_backend->addVideo($this->_getSessionId(), null, $advertRkId, $data);

        return new Dalten_Export_Api_ServerResponseWithVideoChange(
            $response->status == 200,
            $response->status,
            $response->statusMessage
        );
    }

    /**
     * Smaže video nabídky.
     *
     * @param string $advertRkId RK ID nabídky.
     *
     * @return Dalten_Export_Api_ServerResponse Odpoveď serveru.
     */
    public function removeVideo($advertRkId)
    {
        $response = $this->_backend->delVideo($this->_getSessionId(), null, $advertRkId);

        return new Dalten_Export_Api_ServerResponseWithVideoChange(
            $response->status == 200,
            $response->status,
            $response->statusMessage
        );
    }

    public function removeVideoByVideoId($advertRkId, $videoId)
    {
        // delVideo( session_id as string, advert_id as int, advert_rkid as string, video_id as int )
        $response = $this->_backend->delVideo($this->_getSessionId(), null, $advertRkId, $videoId);

        return new Dalten_Export_Api_ServerResponseWithVideoChange(
            $response->status == 200,
            $response->status,
            $response->statusMessage
        );
    }

    /**
     * {@inheritDoc}
     */
    public function listListings()
    {
        $response = $this->_backend->listAdvert($this->_getSessionId());

        if (!isset($response->status)) {
            return new Dalten_Export_Api_ServerResponse(false, 500, 'Špatný formát odpovědi serveru.');
        }

        $output = [];

        if (isset($response->status) && $response->status == 200 && !empty($response->output)) {
            foreach ($response->output as $advert) {
                $output[] = [
                    // kód a URL nabídky ve formátu pro i3
                    'kod' => $advert['advert_rkid'],
                    'detailUrl' => $advert['advert_url'],
                    // původní údaje o nabídce na Srealitách (Sreality - specific)
                    'advert_id' => $advert['advert_id'],
                    'published' => $advert['published'],
                    'published_status' => $advert['published_status'],
                    'top' => $advert['top'],
                ];
            }
        }

        return new Dalten_Export_Api_ServerResponse(
            $response->status == 200,
            $response->status,
            $response->statusMessage ?? '',
            $output
        );
    }

    /**
     * Zkontroluje, zda máme u pronájmů vyplněné datum k nastěhování.
     *
     * @param array $listingData Data nabídky.
     *
     * @return bool Je pronájem v pořádku?
     */
    protected function _checkReadyDate($listingData)
    {
        return $listingData['advert_function'] == 1 || $listingData['advert_function'] == 4 || ($listingData['advert_function'] == 2 && !empty($listingData['ready_date']));
    }

    /**
     * Zkontroluje, zda je vyplněna položka Převod do OV.
     *
     * @param array $listingData Data nabídky.
     *
     * @return bool Je položka Převod do OV v pořádku?
     */
    protected function _checkPersonal($listingData)
    {
        if ($listingData['advert_function']==1 && $listingData['ownership']==2 && isset($listingData['personal']) && $listingData['personal']==-1) {
            return false;
        }
        return true;
    }

    /**
     * Extension point pro odlišný převod uživatele v sreality klonech.
     *
     * @param array<string, scalar|null> $userData
     *
     * @return array<string, string|int>
     */
    protected function _convertUserData(array $userData)
    {
        $photo = null;
        if (!empty($userData['foto'])) {
            $photo = (string) realpath($userData['foto']);
            if ($photo) {
                $photo = file_get_contents($photo);
            }
        }

        $jmenoCele = $userData['uzivatel_os_jmeno'] . (!empty($userData['uzivatel_os_prostredni_jmeno']) ? ' ' . $userData['uzivatel_os_prostredni_jmeno'] : '') . ' ' . $userData['uzivatel_os_prijmeni'];

        if (!empty($userData['uzivatel_tituly_pred_jmenem'])) {
            $jmenoCele = $userData['uzivatel_tituly_pred_jmenem'] . ' ' . $jmenoCele;
        }

        if (!empty($userData['uzivatel_tituly_za_jmenem'])) {
            $jmenoCele = $jmenoCele . ' ' . $userData['uzivatel_tituly_za_jmenem'];
        }

        $userData = [
            'client_login' => $userData['sreality_login'],
            'client_name' => $jmenoCele,
            'contact_gsm' => $userData['telefon'],
            'contact_email' => $userData['email'],
            'contact_phone' => empty($userData['pevna_linka']) ? '' : $userData['pevna_linka'],
            'makler_note' => $userData['moto'],
            'client_ic' => $userData['makler_ico'],
            'client_is_employee' => $userData['makler_je_zamestnanec_na_hpp'],
        ];

        if ($photo) {
            $userData['photo'] = $photo;
        }

        return $userData;
    }

    /**
     * @param array<string, mixed> $listingData
     * @param array<string, mixed> $additionalParams
     *
     * @return array<string, mixed>
     */
    protected function _convertListingData(array $listingData, array $additionalParams)
    {
        $descriptionEn = $this->_findDescriptionTranslation($listingData);
        $priceNoteEn = $this->_findPriceNoteTranslation($listingData);

        if ($this->_optionalAdressConverter) {
            $listingData = $this->_optionalAdressConverter->convertAddress($listingData);
        }

        $listingData = $this->_export->convertEntityValues('listing', $listingData, $this->_config->listing);

        if (!empty($additionalParams['listing']['hide_price'])) {
            $listingData['advert_price'] = (float) 0;
            $priceNote = isset($listingData['advert_price_text_note'])
                ? trim($listingData['advert_price_text_note']) : '';
            if (empty($priceNote)) {
                $listingData['advert_price_text_note'] = 'Informace v RK';
            }
            if (!empty($descriptionEn)) {
                $listingData['advert_price_text_note_en'] = 'Price on request at the office';
            }
        } else {
            if (!empty($priceNoteEn)) {
                $listingData['advert_price_text_note_en'] = (string) $priceNoteEn;
            }
        }

        // nový checkbox Skrýt panorama (pro i3)
        if (isset($additionalParams['listing']['hide_panorama'])) {
            $listingData['panorama'] = $additionalParams['listing']['hide_panorama'] ? 0 : 1;
        }

        if (
            isset($listingData['energy_performance_attachment'])
        ) {
            $priloha = realpath($listingData['energy_performance_attachment']);
            //nemáme soubor se štítkem (realpath nechá projít prázdnou hodnotu)
            if (empty($listingData['energy_performance_attachment']) || $priloha === false) {
                unset($listingData['energy_performance_attachment']);
            } else {
                $listingData['energy_performance_attachment'] = file_get_contents($listingData['energy_performance_attachment']);
            }
        }

        if (!empty($descriptionEn)) {
            $listingData['description_en'] = $descriptionEn;
        }

        return $listingData;
    }

    public function listVideos($advertRkid)
    {
        $response = $this->_backend->listVideos(
            $this->_getSessionId(),
            null,
            $advertRkid
        );

        return new Dalten_Export_Api_ServerResponse(
            $response->status == 200,
            $response->status,
            $response->statusMessage,
            $response->output
        );
    }

    public function videoStatus(array $listingData, array $additionalParams = array())
    {
        $response = $this->_backend->listVideos(
            $this->_getSessionId(),
            null,
            isset($listingData['kod']) ? $listingData['kod'] : null
        );

        return new Dalten_Export_Api_ServerResponse(
            $response->status == 200,
            $response->status,
            $response->statusMessage,
            $response->output
        );
    }
}
