<?php

class DXEvidenceSync
{
    private $backendCommunication;
    private $dataAccess;

    function  __construct()
    {
        $this->backendCommunication = new DXBackendCommunication();
        $this->dataAccess = new DXDataAccess();
    }

    public function loadDataAndProcessCrudBuckets(DXCctTableEnum $tableEnum): array
    {
        $dataToInsert = array();
        $dataToUpdate = array();
        $dataToDelete = array();

        //TODO -> change this to load data smarter (over DXCctTableEnum)...
        $entriesBackend = $this->loadEvents();

        $entriesDb = $this->dataAccess->loadIdAndObjectIdAndGeaendertAm($tableEnum);

        foreach ($entriesBackend as $entryBackend) {

            $filtered = array_filter($entriesDb, function ($entryDb) use ($entryBackend) {
                if ($entryDb['objectid'] == $entryBackend['objectid']) {
                    return $entryDb;
                }
            });

            if (!empty($filtered)) {

                $entryDb = array_values($filtered)[0];

                //ObjectID was found so check if the GeaendertAm was changed
                if ($entryDb['geaendertam'] != $entryBackend['geaendertam']) {
                    $dataToUpdate[] = $entryBackend;
                } else {
                    //ModificationDate did not change so nothing to do here (99%-case)
                }
            } else {
                //ObjectID was not found in DB so the entry is inserted into DB
                $dataToInsert[] = $entryBackend;
            }
        }

        foreach ($entriesDb as $entryDb) {

            $filtered = array_filter($entriesBackend, function ($entryBackend) use ($entryDb) {
                if ($entryBackend['objectid'] == $entryDb['objectid']) {
                    return $entryDb;
                }
            });

            if (empty($filtered)) {
                $dataToDelete[] = $entryDb;
            }
        }

        return array("dataToInsert" => $dataToInsert, "dataToUpdate" => $dataToUpdate, "dataToDelete" => $dataToDelete);
    }

    public function processSync()
    {
        $result = true;

        $isServerAlive = $this->backendCommunication->ping();
        if (!$isServerAlive) {
            $endpoint = $this->backendCommunication->getRestApiOptions()->endpoint;
            error_log("The evidence-backend under '$endpoint' did not answer (on ping). DataSync was not processed.");
            return false;
        }

        try {

            $da = new DXDataAccess();

            //cleanup
            $da->cleanupDatabaseFromDanceXData();

            //import data
            $events = $this->loadEvents();
            $this->insertData($events, "titel", DXType::GetType(DXTypeEnum::Event));

            $kursgruppen = $this->loadKursgruppen();
            $this->insertData($kursgruppen, "name", DXType::GetType(DXTypeEnum::Kursgruppe));

            $kurstypen = $this->loadKurstypen();
            $this->insertData($kurstypen, "name", DXType::GetType(DXTypeEnum::Kurstyp));

            $kursstufen = $this->loadKursstufen();
            $this->insertData($kursstufen, "name", DXType::GetType(DXTypeEnum::Kursstufe));

            $kursleiter = $this->loadKursleiter();
            $this->insertData($kursleiter, "vorname", DXType::GetType(DXTypeEnum::Kursleiter));

            $kurse = $this->loadKurse();
            $this->insertData($kurse, "name", DXType::GetType(DXTypeEnum::Kurs));

            //process relations
            $this->processKurseRelations($kurse, $kursleiter, $kursstufen, $kurstypen, $kursgruppen);
        } catch (\Throwable $th) {
            $result = false;
            error_log("Exception occured while processing 'processSync':" . $th->getMessage());
        }

        return $result;
    }


    private function processKurseRelations($kurse, $kursleiter, $kursstufen, $kurstypen, $kursgruppen)
    {
        $da =  new DXDataAccess();
        $relationsArr = $da->loadJetEngineDanceXRelationIds();

        $relId_kursleiter = $relationsArr[DXTypeEnum::Kursleiter->value];
        $relId_kursgruppe  = $relationsArr[DXTypeEnum::Kursgruppe->value];
        $relId_kursstufe  = $relationsArr[DXTypeEnum::Kursstufe->value];
        $relId_kurstyp  = $relationsArr[DXTypeEnum::Kurstyp->value];

        global $wpdb;
        $relationTable = DXCctTableEnum::wpdb_prefix(DXCctTableEnum::JetRelDefault);

        foreach ($kurse as $kurs) {

            $kursId = $kurs['insert_id'];

            //array_values = reapply 0-indexed array
            //Kursgruppen
            $matchedkursGruppen = array_values(array_filter($kursgruppen, function ($kursgruppe) use ($kurs) {
                if ($kursgruppe['objectid'] == $kurs['fkkursgruppe']) {
                    return true;
                }
            }));

            if (count($matchedkursGruppen) >= 1) {
                $kursgruppeId = $matchedkursGruppen[0]['insert_id'];

                $wpdb->insert($relationTable, array(
                    "rel_id" => $relId_kursgruppe,
                    "parent_rel" => 0,
                    "parent_object_id" => $kursgruppeId,
                    "child_object_id" => $kursId
                ));
            }

            //-----------

            //Kursstufen
            $matchedKursstufen = array_values(array_filter($kursstufen, function ($kursstufe) use ($kurs) {
                if ($kursstufe['objectid'] == $kurs['fkkursstufe']) {
                    return true;
                }
            }));

            if (count($matchedKursstufen) >= 1) {
                $kursstufeId = $matchedKursstufen[0]['insert_id'];

                $wpdb->insert($relationTable, array(
                    "rel_id" => $relId_kursstufe,
                    "parent_rel" => 0,
                    "parent_object_id" => $kursstufeId,
                    "child_object_id" => $kursId
                ));
            }

            //-----------

            //Kurstypen
            $matchedKurstypen = array_values(array_filter($kurstypen, function ($kurstyp) use ($kurs) {
                if ($kurstyp['objectid'] == $kurs['fkkurstyp']) {
                    return true;
                }
            }));

            if (count($matchedKurstypen) >= 1) {
                $kurstypId = $matchedKurstypen[0]['insert_id'];

                $wpdb->insert($relationTable, array(
                    "rel_id" => $relId_kurstyp,
                    "parent_rel" => 0,
                    "parent_object_id" => $kurstypId,
                    "child_object_id" => $kursId
                ));
            }

            //-----------

            //Kursleiter
            $kursleiter_objectids = unserialize($kurs['kursleiter_objectids']);

            foreach ($kursleiter_objectids as $kursleiter_objectid) {

                $matchedKursleiter = array_values(array_filter($kursleiter, function ($kursleiterItem) use ($kursleiter_objectid) {
                    if ($kursleiterItem['objectid'] == $kursleiter_objectid['kursleiter_objectid']) {
                        return true;
                    }
                }));

                if (count($matchedKursleiter) == 1) {
                    $kursleiterId = $matchedKursleiter[0]['insert_id'];

                    $wpdb->insert($relationTable, array(
                        "rel_id" => $relId_kursleiter,
                        "parent_rel" => 0,
                        "parent_object_id" => $kursleiterId,
                        "child_object_id" => $kursId
                    ));
                }
            }
        }
    }

    private function loadKurse(): array
    {
        $kurseForDb = array();

        try {

            $kurse = $this->backendCommunication->getKurse();

            //Mapping JSON-Data to DB-fields
            foreach ($kurse as $kurs) {

                $kursdaten = array();
                $itemCount = 0;
                foreach ($kurs['Kursdaten'] as $oneKursData) {

                    $item = array(
                        "wochentag" => $this->loadkey("Wochentag", $oneKursData),
                        "von" => $this->loadKeyParseDatetime("Von", $oneKursData),
                        "bis" => $this->loadKeyParseDatetime("Bis", $oneKursData),
                        "datum" => $this->loadkey("Datum", $oneKursData),
                        "dauer" => $this->loadkey("Dauer", $oneKursData),
                        "kursraum" => $this->loadkey("Kursraum", $oneKursData),
                        "nummer" => $this->loadkey("Nummer", $oneKursData),
                        "abmeldegrund" => $this->loadkey("AbmeldeGrund", $oneKursData),
                    );

                    $kursdaten["item-$itemCount"] = $item;

                    $itemCount++;
                }

                //---

                $kursleiterIds = array();
                $itemCount = 0;
                foreach ($kurs['KursleiterIds'] as $oneKursleiterId) {

                    $item = array(
                        "kursleiterid" => $oneKursleiterId
                    );

                    $kursleiterIds["item-$itemCount"] = $item;

                    $itemCount++;
                }

                //---

                $kursleiter_objectids = array();
                $itemCount = 0;
                foreach ($kurs['FkKursleiter'] as $oneKursleiterObjectId) {

                    $item = array(
                        "kursleiter_objectid" => $oneKursleiterObjectId
                    );

                    $kursleiter_objectids["item-$itemCount"] = $item;

                    $itemCount++;
                }

                $kurseForDb[] = array(
                    "objectid" => $this->loadkey("ObjectId", $kurs),
                    "geaendertam" => $this->loadkey("GeaendertAm", $kurs),
                    "kursleiter" => $this->loadkey("Kursleiter", $kurs),
                    "fkkursgruppe" => $this->loadkey("FkKursgruppe", $kurs),
                    "kursgruppe" => $this->loadkey("Kursgruppe", $kurs),
                    "fkkurstyp" => $this->loadkey("FkKurstyp", $kurs),
                    "kurstyp" => $this->loadkey("Kurstyp", $kurs),
                    "fkkursstufe" => $this->loadkey("FkKursstufe", $kurs),
                    "kursstufe" => $this->loadkey("Kursstufe", $kurs),
                    "kursperiode" => $this->loadkey("Kursperiode", $kurs),
                    "sortierung" => $this->loadkey("Sortierung", $kurs),
                    "preis" =>  $this->loadKeyParseDecimal("Preis", $kurs),
                    "kleinklassenzuschlag" => $this->loadKeyParseDecimal("Kleinklassenzuschlag", $kurs),
                    "minimumteilnehmerkleinklasse" => $this->loadkey("MinimumTeilnehmerKleinklasse", $kurs),
                    "singleanmeldungzwingend" => $this->loadkey("SingleAnmeldungZwingend", $kurs),
                    "paaranmeldungzwingend" => $this->loadkey("PaarAnmeldungZwingend", $kurs),
                    "paartanz" => $this->loadkey("Paartanz", $kurs),
                    "paarrabatt" => $this->loadKeyParseDecimal("PaarRabatt", $kurs),
                    "schuelerstudentenrabatt" => $this->loadKeyParseDecimal("SchuelerStudentenRabatt", $kurs),
                    "viprabatt" => $this->loadKeyParseDecimal("VIPRabatt", $kurs),
                    "wiederholungsrabatt" => $this->loadKeyParseDecimal("WiederholungsRabatt", $kurs),
                    "kursanmeldungstopvoll" => $this->loadkey("KursAnmeldungStopVoll", $kurs),
                    "freieplaetzemann" => $this->loadkey("FreiePlaetzeMann", $kurs),
                    "freieplaetzefrau" => $this->loadkey("FreiePlaetzeFrau", $kurs),
                    "freieplaetze" => $this->loadkey("FreiePlaetze", $kurs),
                    "kursnummer" => $this->loadkey("Kursnummer", $kurs),
                    "name" => $this->loadkey("Name", $kurs),
                    "kurzzeichen" => $this->loadkey("Kurzzeichen", $kurs),
                    "anzahllektionen" => $this->loadkey("AnzahlLektionen", $kurs),
                    "ersterkurstag" => $this->loadkey("ErsterKurstag", $kurs),
                    "letzterkurstag" => $this->loadkey("LetzterKurstag", $kurs),
                    "kursdatenauflistung" => $this->loadkey("KursdatenAuflistung", $kurs),
                    "kursleiterids" => serialize($kursleiterIds),
                    "kursleiter_objectids" => serialize($kursleiter_objectids),
                    "kursdaten" => serialize($kursdaten)
                );
            }
        } catch (\Throwable $th) {
            error_log("Exception occured while processing 'loadKurse':" . $th->getMessage());
            throw $th;
        }

        return  $kurseForDb;
    }

    private function loadEvents(): array
    {
        $eventsForDb = array();

        try {

            $events = $this->backendCommunication->getAllEvents();

            //Mapping JSON-Data to DB-fields
            foreach ($events as $event) {

                $flyer = $this->loadkey("Flyer", $event);

                $vorverkaufspreis = $this->loadkey("Vorverkaufspreis", $event);
                if ($vorverkaufspreis)
                    $vorverkaufspreis = str_replace(',', '.', $vorverkaufspreis);

                $abendkasse = $this->loadkey("Abendkasse", $event);
                if ($abendkasse)
                    $abendkasse = str_replace(',', '.', $abendkasse);

                $addedEntry = array(
                    "objectid" => $this->loadkey("ObjectId", $event),
                    "geaendertam" => $this->loadkey("GeaendertAm", $event),
                    "adresse" => $this->loadkey("Adresse", $event),
                    "beschreibung" => $this->loadkey("Beschreibung", $event),
                    "bis" => $this->loadkey("Bis", $event),
                    "datum" => $this->loadkey("Datum", $event),
                    "eventnummer" => $this->loadkey("Eventnummer", $event),
                    "kategorie" => $this->loadkey("Kategorie", $event),
                    "link" => $this->loadkey("Link", $event),
                    "ort" => $this->loadkey("Ort", $event),
                    "vorverkaufspreis" => $vorverkaufspreis,
                    "abendkasse" => $abendkasse,
                    "titel" => $this->loadkey("Titel", $event),
                    "von" => $this->loadkey("Von", $event),
                    "flyer_Dateiname" =>  $this->loadkey("Dateiname", $flyer),
                    "flyer_DokumentId" =>  $this->loadkey("DokumentId", $flyer),
                    "flyer_BildId" =>  $this->loadkey("BildId", $flyer),
                );

                if ($addedEntry["flyer_DokumentId"] != null && $addedEntry["flyer_Dateiname"] != null) {

                    $flyer_DokumentId = $addedEntry["flyer_DokumentId"];

                    $base64Doc = $this->backendCommunication->getDocument($flyer_DokumentId);
                    if ($base64Doc != null) {
                        $attachId = DXUtil::getAttachmentIdFromPreviouslyUploadedMedia($base64Doc);
                        if ($attachId == null) {
                            $attachId = DXUtil::importDocumentToMediaLibrary($addedEntry["flyer_Dateiname"], $base64Doc);
                        }
                        $addedEntry["flyer_Dokument"] = $attachId;
                    }
                }

                if ($addedEntry["flyer_BildId"] != null) {

                    $flyer_BildId = $addedEntry["flyer_BildId"];

                    $base64Img = $this->backendCommunication->getImage($flyer_BildId);
                    if ($base64Img != null) {
                        $attachId = DXUtil::getAttachmentIdFromPreviouslyUploadedMedia($base64Img);
                        if ($attachId == null) {
                            $attachId = DXUtil::importImageToMediaLibrary($base64Img);
                        }
                        $addedEntry["flyer_Bild"] = $attachId;
                    }
                }

                $eventsForDb[] = $addedEntry;
            }
        } catch (\Throwable $th) {
            error_log("Exception occured while processing 'loadEvents':" . $th->getMessage());
            throw $th;
        }

        return $eventsForDb;
    }

    private function loadKursgruppen(): array
    {
        $kursgruppenForDb = array();

        try {

            $kursgruppen = $this->backendCommunication->getAllKursgruppe();

            //Mapping JSON-Data to DB-fields
            foreach ($kursgruppen as $kursgruppe) {

                $addedEntry =  array(
                    "objectid" => $this->loadkey("ObjectId", $kursgruppe),
                    "geaendertam" => $this->loadkey("GeaendertAm", $kursgruppe),
                    "kurzzeichen" => $this->loadkey("Kurzzeichen", $kursgruppe),
                    "name" => $this->loadkey("Name", $kursgruppe),
                    "webtext" => $this->loadkey("WebText", $kursgruppe),
                    "teasertext" => $this->loadkey("Teasertext", $kursgruppe),
                    "sortierung" => $this->loadkey("Sortierung", $kursgruppe),
                    "webfiltera" => $this->loadkey("WebFilterA", $kursgruppe),
                    "webfilterb" => $this->loadkey("WebFilterB", $kursgruppe),
                    "bild" => $this->loadkey("Bild", $kursgruppe)
                );

                if ($addedEntry["bild"] != null) {

                    $base64Img = $addedEntry["bild"];

                    $attachId = DXUtil::getAttachmentIdFromPreviouslyUploadedMedia($base64Img);
                    if ($attachId == null) {
                        $attachId = DXUtil::importImageToMediaLibrary($base64Img);
                    }
                    $addedEntry["bild"] = $attachId;
                }

                $kursgruppenForDb[] = $addedEntry;
            }
        } catch (\Throwable $th) {
            error_log("Exception occured while processing 'loadKursgruppe':" . $th->getMessage());
            throw $th;
        }

        return $kursgruppenForDb;
    }

    private function loadKurstypen(): array
    {
        $kurstypenForDb = array();

        try {

            $kurstypen = $this->backendCommunication->getAllKurstyp();

            //Mapping JSON-Data to DB-fields
            foreach ($kurstypen as $kurstyp) {

                $addedEntry = array(
                    "objectid" => $this->loadkey("ObjectId", $kurstyp),
                    "geaendertam" => $this->loadkey("GeaendertAm", $kurstyp),
                    "kurzzeichen" => $this->loadkey("Kurzzeichen", $kurstyp),
                    "name" => $this->loadkey("Name", $kurstyp),
                    "webtext" => $this->loadkey("WebText", $kurstyp),
                    "sortierung" => $this->loadkey("Sortierung", $kurstyp),
                    "top4" => $this->loadkey("Top4", $kurstyp),
                    "bild" => $this->loadkey("Bild", $kurstyp)
                );

                if ($addedEntry["bild"] != null) {

                    $base64Img = $addedEntry["bild"];

                    $attachId = DXUtil::getAttachmentIdFromPreviouslyUploadedMedia($base64Img);
                    if ($attachId == null) {
                        $attachId = DXUtil::importImageToMediaLibrary($base64Img);
                    }
                    $addedEntry["bild"] = $attachId;
                }

                $kurstypenForDb[] = $addedEntry;
            }
        } catch (\Throwable $th) {
            error_log("Exception occured while processing 'loadKurstypen':" . $th->getMessage());
            throw $th;
        }

        return $kurstypenForDb;
    }

    private function loadKursstufen(): array
    {
        $kursstufenForDb = array();

        try {

            $kursstufen = $this->backendCommunication->getAllKursstufe();

            //Mapping JSON-Data to DB-fields
            foreach ($kursstufen as $kursstufe) {

                $addedEntry = array(
                    "objectid" => $this->loadkey("ObjectId", $kursstufe),
                    "geaendertam" => $this->loadkey("GeaendertAm", $kursstufe),
                    "kurzzeichen" => $this->loadkey("Kurzzeichen", $kursstufe),
                    "name" => $this->loadkey("Name", $kursstufe),
                    "webtext" => $this->loadkey("WebText", $kursstufe),
                    "teasertext" => $this->loadkey("TeaserText", $kursstufe),
                    "sortierung" => $this->loadkey("Sortierung", $kursstufe),
                    "top4" => $this->loadkey("Top4", $kursstufe),
                    "bild" => $this->loadkey("Bild", $kursstufe)
                );

                if ($addedEntry["bild"] != null) {

                    $base64Img = $addedEntry["bild"];

                    $attachId = DXUtil::getAttachmentIdFromPreviouslyUploadedMedia($base64Img);
                    if ($attachId == null) {
                        $attachId = DXUtil::importImageToMediaLibrary($base64Img);
                    }
                    $addedEntry["bild"] = $attachId;
                }

                $kursstufenForDb[] = $addedEntry;
            }
        } catch (\Throwable $th) {
            error_log("Exception occured while processing 'loadKursstufen':" . $th->getMessage());
            throw $th;
        }

        return $kursstufenForDb;
    }

    private function loadKursleiter(): array
    {
        $kursleiterForDb = array();

        try {

            $kursleiters = $this->backendCommunication->getAllKursleiterDetail();

            //Mapping JSON-Data to DB-fields
            foreach ($kursleiters as $kursleiter) {

                $addedEntry = array(
                    "objectid" => $this->loadkey("ObjectId", $kursleiter),
                    "geaendertam" => $this->loadkey("GeaendertAm", $kursleiter),
                    "id" => $this->loadkey("Id", $kursleiter),
                    "name" => $this->loadkey("Name", $kursleiter),
                    "vorname" => $this->loadkey("Vorname", $kursleiter),
                    "funktion" => $this->loadkey("Funktion", $kursleiter),
                    "webtext" => $this->loadkey("WebText", $kursleiter),
                    "sortierung" => $this->loadkey("Sortierung", $kursleiter),
                    "foto" => $this->loadkey("Foto", $kursleiter)
                );

                if ($addedEntry["foto"] != null) {

                    $base64Img = $addedEntry["foto"];

                    $attachId = DXUtil::getAttachmentIdFromPreviouslyUploadedMedia($base64Img);
                    if ($attachId == null) {
                        $attachId = DXUtil::importImageToMediaLibrary($base64Img);
                    }
                    $addedEntry["foto"] = $attachId;
                }

                $kursleiterForDb[] = $addedEntry;
            }
        } catch (\Throwable $th) {
            error_log("Exception occured while processing 'loadKursleiter':" . $th->getMessage());
            throw $th;
        }

        return  $kursleiterForDb;
    }


    //the & (reference operator) is needed or the insert_id-update does not work
    private function insertData(array &$rows, string $colToTakeTitleFrom, DXType $dxType)
    {
        try {
            global $wpdb;
            $prefixedTable = DXCctTableEnum::wpdb_prefix($dxType->tableEnum);

            foreach ($rows as &$row) {

                $postTitle = 'no title set';
                if (array_key_exists($colToTakeTitleFrom, $row))
                    $postTitle = $row[$colToTakeTitleFrom];

                //post_name (slug) is automatically sanitized 
                $createdPost =  wp_insert_post(array(
                    "post_author" => 1, //should be always admin -> todo:matz -> is this smart? Is there a better way?
                    "post_title" => $postTitle,
                    "post_name" => $postTitle,
                    "post_status" => "publish",
                    "post_type" => $dxType->typeEnum->value
                ), true, false);

                if (is_wp_error($createdPost)) {
                    throw new Exception(implode("|", $createdPost->get_error_messages()));
                }

                //so that all CCT are directly published
                $row['cct_status'] = 'publish';
                //and link the CCT with the "shadow-behind-post"
                $row['cct_single_post_id'] = $createdPost;

                //effectively insert the CCT-row
                $wpdb->insert($prefixedTable, $row);

                if ($wpdb->insert_id == false)
                    throw new Exception("There was a problem inserting a new row!");

                //and add the inserted id to make it easier to create the relations later on
                $row['insert_id'] = $wpdb->insert_id;
            }
        } catch (\Throwable $th) {
            error_log("Exception occured while processing 'insertData' of table {$prefixedTable}:" . $th->getMessage());
            throw $th;
        }
    }

    private function loadkey($key, $arr)
    {
        if ($key == null || $arr == null)
            return null;

        return array_key_exists($key, $arr) ? $arr[$key] : null;
    }

    private function loadKeyParseDecimal($key, $arr)
    {
        $entry = $this->loadkey($key, $arr);
        if ($entry == null)
            return null;

        return str_replace(',', '.', $entry);
    }

    private function loadKeyParseDatetime($key, $arr)
    {
        $entry = $this->loadkey($key, $arr);
        if ($entry == null)
            return null;

        $dateTime = DateTime::createFromFormat("Y-m-d H:i:s", $entry);
        if ($dateTime !== false) {
            return $dateTime->format("Y-m-d H:i");
        }

        return null;
    }
}
