<?php

namespace App\Services;

use App\Models\Backoffice\WebsiteObject;
use App\Models\Backoffice\WebsiteObjectLanguage;
use App\Models\Competition;
use App\Models\CompetitionLanguage;
use App\Models\Country;
use App\Models\Cpv;
use App\Models\Nuts3Region;
use App\Models\ProcedureType;
use Carbon\Carbon;
use DOMDocument;
use DOMElement;
use DOMXPath;
use Exception;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
use Saloon\XmlWrangler\XmlReader;
use Throwable;
use Illuminate\Support\Str;

class TedImportService
{
    const TED_QUERY = 'NC=(services or 9 or Z) AND PC=(90700000 or 92300000 or 70000000 or 71000000 or 45200000 or 92521100 or 92522000) AND PR=(comp-dial or comp-tend or oth-mult or neg-w-call or oth-single or neg-wo-call or open or restricted) AND notice-type=(pin-buyer or cn-social or corr or pin-rtl or can-modif or cn-desg or pin-cfc-social or pin-cfc-standard or cn-standard or pin-only)';
    const TED_API_BASE_URL = 'https://api.ted.europa.eu/v3/';
    const TED_API_SEARCH_ENDPOINT = 'notices/search';
    const TED_LANGUAGE_MAP = [
        'BUL' => 'bg',
        'CES' => 'cs',
        'DAN' => 'da',
        'DEU' => 'de',
        'ELL' => 'el',
        'ENG' => 'en',
        'EST' => 'et',
        'FIN' => 'fi',
        'FRA' => 'fr',
        'GLE' => 'ga',
        'HRV' => 'hr',
        'ITA' => 'it',
        'LAV' => 'lv',
        'LIT' => 'lt',
        'MLT' => 'mt',
        'NLD' => 'nl',
        'POL' => 'pl',
        'POR' => 'pt',
        'RON' => 'ro',
        'SLK' => 'sk',
        'SLV' => 'sl',
        'SPA' => 'es',
        'SWE' => 'sv',
        'HUN' => 'hu'
    ];
    const TED_ITEMS_PER_PAGE = 150;

    protected array $baseUri;

    public function import()
    {
        $competitionConfig = config('backoffice.contentTypes.competition');
        $this->baseUri = $competitionConfig['baseUri'];

        $client = new Client([
            'base_uri' => self::TED_API_BASE_URL,
            'timeout'  => 10.0,
        ]);

        $page = 1;
        $stop = false;
        do {
            $response = $client->post(self::TED_API_SEARCH_ENDPOINT, [
                'json' => [
                    'query' => self::TED_QUERY,
                    'fields' => ["publication-number"],
                    'scope' => 'LATEST',
                    'page' => $page,
                    'limit' => self::TED_ITEMS_PER_PAGE
                ],
                'headers' => [
                    'Accept' => 'application/json',
                    'Content-Type'
                ],
            ]);

            // Get the status code
            $statusCode = $response->getStatusCode();

            // Check if the request was successful
            if ($statusCode === 200) {
                // Decode JSON response
                $data = json_decode($response->getBody(), true);

                Log::info("Processing page {$page} with " . count($data['notices']) . " notices.");

                foreach ($data['notices'] as $notice) {
                    try {
                        $this->importNotice($notice);
                    } catch (Throwable $e) {
                        Log::error('Error importing notice ' . $notice['publication-number'], ['exception' => $e]);
                    }
                }

                $total = $data['totalNoticeCount'];
                $stop = $total <= $page * self::TED_ITEMS_PER_PAGE;
                $page++;
                sleep(5);
            } else {
                echo "Error: Received HTTP status code " . $statusCode;
            }
        } while ($page < 99 && !$stop);
        Log::info("TED import completed.");
    }

    public function importByPublicationNumber(string $publicationNumber)
    {
        $competitionConfig = config('backoffice.contentTypes.competition');
        $this->baseUri = $competitionConfig['baseUri'];

        $client = new Client([
            'base_uri' => self::TED_API_BASE_URL,
            'timeout'  => 10.0,
        ]);

        $response = $client->post(self::TED_API_SEARCH_ENDPOINT, [
            'json' => [
                'query' => "publication-number={$publicationNumber}",
                'fields' => ["publication-number"],
                'scope' => 'ALL',
                'page' => 1,
                'limit' => 1
            ],
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type'
            ],
        ]);

        $statusCode = $response->getStatusCode();

        if ($statusCode !== 200) {
            Log::error("TED API returned HTTP {$statusCode} for publication number {$publicationNumber}");
            return;
        }

        $data = json_decode($response->getBody(), true);

        if (empty($data['notices'])) {
            Log::warning("No notice found for publication number {$publicationNumber}");
            return;
        }

        $this->importNotice($data['notices'][0]);
    }

    protected function getInnerHTML(DOMElement $node)
    {
        $doc = new DOMDocument();
        $doc->appendChild($doc->importNode($node, true));
        return $doc->saveHTML();
    }

    protected function safeGet(Array|null $array, string $path, $defaultValue = null)
    {
        if (!$array) {
            return $defaultValue;
        }
        $path = explode('.', $path);
        foreach ($path as $pathPart) {
            if (empty($array[$pathPart])) {
                return $defaultValue;
            }
            $array = $array[$pathPart];
        }

        return $array;
    }

    protected function importNotice(Array $notice)
    {
        $pubNumber = $notice['publication-number'];
        Log::info('Importing notice number ' . $pubNumber);

        if (Competition::where('wa_id', 'wa-' . $pubNumber)->count()) {
            Log::info('Skipping - Already exists - ' . $pubNumber);
            return false;
        }
        DB::beginTransaction();
        try {
            $xmlUrl = $notice['links']['xml']['MUL'];
            $htmlUrl = $notice['links']['htmlDirect']['DEU'];
            $competitionUrl = $notice['links']['html']['DEU'];

            $xmlContent = file_get_contents($xmlUrl);
            sleep(1);
            $htmlContent = file_get_contents($htmlUrl);

            if ($xmlContent === false) {
                throw new Exception("Failed to fetch XML from URL: $xmlUrl");
            }
            if (!$htmlContent) {
                throw new Exception("Failed to fetch HTML from URL: $xmlUrl");
            }
            $doc = new DOMDocument();
            $doc->loadHTML($htmlContent);
            $xpath = new DOMXPath($doc);
            foreach ($xpath->query('//comment()') as $comment) {
                $comment->parentNode->removeChild($comment);
            }

            $noticeDiv = $doc->getElementById('notice');
            $htmlContent = $this->getInnerHTML($noticeDiv);

            $reader = XmlReader::fromString($xmlContent);
            $reader->removeNamespaces();

            $project = $reader->xpathValue('ProcurementProject')->sole();
            $name = $project['Name'];
            $description = $project['Description'];
            $note = !empty($project['Note']) ? $project['Note'] : '';
            $cpvs = $reader->xpathValue('ProcurementProject//ItemClassificationCode[@listName="cpv"]')->get();
            $publicationDate = $reader->value('UBLExtensions.UBLExtension.ExtensionContent.EformsExtension.Publication.PublicationDate.0')->sole();
            $submissionDate = $reader->value('ProcurementProjectLot.TenderingProcess.ParticipationRequestReceptionPeriod.EndDate')->get();
            $submissionTime = $reader->value('ProcurementProjectLot.TenderingProcess.ParticipationRequestReceptionPeriod.EndTime')->get();

            if (!$submissionDate) {
                $submissionDate = $reader->value('ProcurementProjectLot.TenderingProcess.TenderSubmissionDeadlinePeriod.EndDate')->get();
                $submissionTime = $reader->value('ProcurementProjectLot.TenderingProcess.TenderSubmissionDeadlinePeriod.EndTime')->get();
            }
            if (!$submissionDate) {
                $submissionDate = $reader->value('UBLExtensions.UBLExtension.ExtensionContent.EformsExtension.InterestExpressionReceptionPeriod.EndDate')->get();
                $submissionTime = $reader->value('UBLExtensions.UBLExtension.ExtensionContent.EformsExtension.InterestExpressionReceptionPeriod.EndTime')->get();
            }

            $additionalInformationDate = $reader->value('ProcurementProjectLot.TenderingProcess.AdditionalInformationRequestPeriod.EndDate')->get();
            $additionalInformationTime = $reader->value('ProcurementProjectLot.TenderingProcess.AdditionalInformationRequestPeriod.EndTime')->get();

            $realizedLocation = $reader->xpathValue('ProcurementProject/RealizedLocation')->get();
            if (empty($realizedLocation[0])) {
                $realizedLocation = $reader->xpathValue('ProcurementProjectLot/ProcurementProject/RealizedLocation')->get();
            }

            $locationCountryCode = $this->safeGet($realizedLocation, '0.Address.Country.IdentificationCode');
            $locationNutsCode = $this->safeGet($realizedLocation, '0.Address.CountrySubentityCode');
            $locationAddress = $this->safeGet($realizedLocation, '0.Address.StreetName');
            $locationCity = $this->safeGet($realizedLocation, '0.Address.CityName');
            $locationZip = $this->safeGet($realizedLocation, '0.Address.PostalZone');

            $originalLanguage = $reader->xpathValue('NoticeLanguageCode')->get();

            $documentsUrl = $reader->xpathValue('ProcurementProjectLot/TenderingTerms/TenderRecipientParty/EndpointID')->get();
            $contractingPartyId = $reader->xpathValue('ContractingParty/Party/PartyIdentification/ID')->get()[0] ?? 'ORG-0001';
            $organization = $reader->xpathValue('UBLExtensions/UBLExtension/ExtensionContent/EformsExtension/Organizations/Organization[Company/PartyIdentification/ID = \'' . $contractingPartyId . '\']')->get();

            if (!$organization) {
                $organization = $reader->xpathValue('UBLExtensions/UBLExtension/ExtensionContent/EformsExtension/Organizations/Organization')->get()[0];
            } else {
                $organization = $organization[0];
            }

            $organizationName = $this->safeGet($organization, 'Company.PartyName.Name');
            $organizationAddress = $this->safeGet($organization, 'Company.PostalAddress.StreetName');
            $organizationCity = $this->safeGet($organization, 'Company.PostalAddress.CityName');
            $organizationZip = $this->safeGet($organization, 'Company.PostalAddress.PostalZone');
            $organizationNutsCode = $this->safeGet($organization, 'Company.PostalAddress.CountrySubentityCode');
            $organizationCountryCode = $this->safeGet($organization, 'Company.PostalAddress.Country.IdentificationCode');

            $contactName = $this->safeGet($organization, 'Company.Contact.Name');
            $contactPhone = $this->safeGet($organization, 'Company.Contact.Telephone');
            $contactEmail = $this->safeGet($organization, 'Company.Contact.ElectronicMail');

            $procedureTypeCode = $reader->xpathValue('TenderingProcess/ProcedureCode')->get();

            $updatedNoticeId = $reader->xpathValue('UBLExtensions/UBLExtension/ExtensionContent/EformsExtension/Changes/ChangedNoticeIdentifier')->get();
            // $changeReasonCode = $reader->xpathValue('UBLExtensions/UBLExtension/ExtensionContent/EformsExtension/Changes/ChangeReason/ReasonCode')->get();

            $noticeId = $reader->xpathValue('ID')->get();
            $noticeTypeCode = $reader->xpathValue('NoticeTypeCode')->get();

            $officialNumber1 = $reader->xpathValue('UBLExtensions/UBLExtension/ExtensionContent/EformsExtension/Publication/GazetteID')->get();
            $officialNumber2 = $reader->xpathValue('UBLExtensions/UBLExtension/ExtensionContent/EformsExtension/Publication/NoticePublicationID')->get();

            $promoterCountry = Country::where('iso3', $organizationCountryCode)->first();
            $locationCountry = Country::where('iso3', $locationCountryCode)->first();

            $competition = new Competition();

            $competition->xml_url = $xmlUrl;
            $competition->html_url = $htmlUrl;
            $competition->competition_url = $competitionUrl;
            $competition->wa_id = 'wa-' . $pubNumber;
            $competition->notice_id = $noticeId[0] ?? null;
            if ($updatedNoticeId) {
                $competition->updated_notice_id = $updatedNoticeId[0];

                if (strlen($competition->updated_notice_id) < 20) {
                    $updatedCompetition = Competition::where('wa_id', 'wa-' . $competition->updated_notice_id)->first();
                    if ($updatedCompetition) {
                        $competition->updated_competition_id = $updatedCompetition->id;
                        $competition->updated_competition_url = $updatedCompetition->competition_url;
                    } else {
                        Log::warning('Updated competition not found for notice ID ' . $competition->updated_notice_id);
                    }
                } else {
                    $updatedCompetitionUUID = substr($competition->updated_notice_id, 0, 36);
                    $updatedCompetition = Competition::where('notice_id', $updatedCompetitionUUID)->first();
                    if ($updatedCompetition) {
                        $competition->updated_competition_id = $updatedCompetition->id;
                        $competition->updated_competition_url = $updatedCompetition->competition_url;
                    } else {
                        $updatedLinks = $this->getTedCompetitionByNoticeId($updatedCompetitionUUID);
                        if (!empty($updatedLinks)) {
                            $competition->updated_competition_url = $updatedLinks['links']['html']['DEU'];
                        } else {
                            Log::warning('Updated competition not found for notice ID ' . $updatedCompetitionUUID);
                        }
                    }
                }
            }
            $competition->promoter = $organizationName ? mb_substr($organizationName, 0, 255) : null;
            $competition->promoter_address = $organizationAddress;
            $competition->promoter_postal_code = $organizationZip;
            $competition->promoter_city = $organizationCity;
            $competition->promoter_nuts_code = $organizationNutsCode;
            $competition->promoterCountry()->associate($promoterCountry);
            $competition->contact_name = $contactName;
            $competition->contact_phone = $contactPhone;
            $competition->contact_email = $contactEmail;

            $competition->original_language_code = self::TED_LANGUAGE_MAP[$originalLanguage[0]];
            $competition->publication_date = new Carbon(mb_substr($publicationDate, 0, 10));

            if ($submissionDate) {
                if ($submissionTime) {
                    $competition->submission_until = (new Carbon(mb_substr($submissionDate[0], 0, 10) . 'T' . $submissionTime[0]))->setTimezone('UTC');
                } else {
                    $competition->submission_until = new Carbon(mb_substr($submissionDate[0], 0, 10) . 'T23:00:00');
                }
            }
            if ($additionalInformationDate) {
                if ($additionalInformationTime) {
                    $competition->information_request_until = (new Carbon(mb_substr($additionalInformationDate[0], 0, 10) . 'T' . $additionalInformationTime[0]))->setTimezone('UTC');
                } else {
                    $competition->information_request_until = new Carbon(mb_substr($additionalInformationDate[0], 0, 10) . 'T23:00:00');
                }
            }

            $procedureTypeQuery = ProcedureType::query();
            if (!in_array($noticeTypeCode[0], ['pin-only', 'pin-cfc-standard', 'pin-cfc-social', 'pin-buyer', 'pin-rtl', 'cn-social'])) {
                $procedureTypeQuery->where('ted_types_map', 'like', '%' . $procedureTypeCode[0] . '%');
            }
            if (!in_array($procedureTypeCode[0], ['oth-single', 'oth-mult', 'neg-wo-call', 'neg-w-call', 'comp-dial'])) {
                $procedureTypeQuery->where('ted_notice_types_map', 'like', '%' . $noticeTypeCode[0] . '%');
            }
            $procedureType = $procedureTypeQuery->first();
            if (!$procedureType && $procedureTypeCode[0] === 'restricted') {
                $procedureType = ProcedureType::where('ted_types_map', 'like', '%restricted%')
                    ->where(function ($query) {
                        $query->whereNull('ted_notice_types_map')
                            ->orWhere('ted_notice_types_map', '');
                    })->first();
            }
            if (!$procedureType) {
                Log::info('Falling back to Sonstiges');
                $procedureType = ProcedureType::where(function ($query) {
                        $query->whereNull('ted_types_map')
                            ->orWhere('ted_types_map', '');
                    })->where('ted_notice_types_map', 'cn-social')->first();
            }
            Log::info('Mapped TED procedure code ' . $procedureTypeCode[0] . ' and notice type ' . $noticeTypeCode[0] . ' to procedure type ' . ($procedureType->title ?? 'null'));

            $pendingProcedureType = $procedureType;
            $competition->procedure_type_code = $procedureTypeCode[0];

            $competition->location_address = $locationAddress;
            $competition->location_postal_code = $locationZip;
            $competition->location_city = $locationCity;
            $competition->location_nuts_code = $locationNutsCode;
            $competition->locationCountry()->associate($locationCountry);

            if ($locationNutsCode) {
                $nuts3Region = Nuts3Region::where('code', $locationNutsCode)->first();
                if ($nuts3Region) {
                    $competition->nuts3Region()->associate($nuts3Region);
                } else {
                    Log::warning('Nuts3 region not found for code ' . $locationNutsCode);
                }
            }

            $competition->official_number = "{$officialNumber1[0]} {$officialNumber2[0]}";

            $competition->description = is_array($description) ? $description[0] : $description;
            $competition->note = is_array($note) ? $note[0] : $note;
            $competition->content = $htmlContent;
            $competition->documents_url = !empty($documentsUrl) ? $documentsUrl[0] : null;
            $competition->meta_title = mb_substr(is_array($name) ? $name[0] : $name, 0, 255);
            $competition->meta_description = $competition->description;

            $competition->save();

            if (isset($pendingProcedureType) && $pendingProcedureType) {
                $competition->procedureTypes()->sync([$pendingProcedureType->id => ['position' => 1]]);
            }

            $competition->cpvs()->sync(Cpv::whereIn('code', $cpvs)->pluck('id'));

            $wo = new WebsiteObject();
            $wo->object_id = $competition->id;
            $wo->object_type = Competition::class;
            $wo->content_type = 'competition';
            $wo->save();

            $languages = LaravelLocalization::getSupportedLanguagesKeys();
            foreach ($languages as $lang) {
                $competitionLanguage = new CompetitionLanguage();
                $competitionLanguage->competition_id = $competition->id;
                $competitionLanguage->language_id = $lang;
                $competitionLanguage->title = mb_substr(is_array($name) ? $name[0] : $name, 0, 255);
                $competitionLanguage->save();

                $wol = new WebsiteObjectLanguage();
                $wol->website_object_id = $wo->id;
                $wol->slug = mb_substr(Str::slug($competitionLanguage->title), 0, 245) . "-w{$competition->id}";
                $wol->uri = "{$this->baseUri[$lang]}/{$wol->slug}";
                $wol->status = WebsiteObject::STATUS_DRAFT;

                $wol->wo_language_id = $lang;
                $wol->save();
            }

            DB::commit();
        } catch (Throwable $e) {
            DB::rollBack();
            Log::error('Error Importing ' . $pubNumber, );
            report($e);
        }
    }

    public function getTedCompetitionByNoticeId(string $noticeId): Array|null
    {
        $client = new Client([
            'base_uri' => self::TED_API_BASE_URL,
            'timeout'  => 10.0,
        ]);

        $response = $client->post(self::TED_API_SEARCH_ENDPOINT, [
            'json' => [
                'query' => "notice-identifier={$noticeId}",
                'fields' => ["publication-number"],
                'scope' => 'ALL',
                'page' => 1,
                'limit' => 1
            ],
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type'
            ],
        ]);

        // Get the status code
        $statusCode = $response->getStatusCode();

        // Check if the request was successful
        if ($statusCode === 200) {
            // Decode JSON response
            $data = json_decode($response->getBody(), true);

            return !empty($data['notices']) ? $data['notices'][0] : null;
        } else {
            Log::warning("Error: Received HTTP status code " . $statusCode . " while trying to fetch notice ID " . $noticeId);
            return [];
        }
    }
}
