<?php

namespace App\Libs\Services;

use App\Libs\Exception\InvalidConfigurationException;
use App\Libs\Facades\Localization;
use App\Mail\ProjectSyncNotification;
use Carbon\Carbon;
use Exception;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use App\Models\Project;
use App\Models\ProjectLanguage;
use App\Models\ProjectImage;
use App\Models\ProjectImageLanguage;
use App\Models\ProjectLink;
use App\Models\ProjectLinkLanguage;
use App\Models\Technology;
use App\Models\TechnologyLanguage;
use App\Models\Standard;
use App\Models\StandardLanguage;
use App\Models\Country;
use App\Models\CountryLanguage;
use App\Models\ProjectCobenefit;
use App\Models\ProjectCobenefitLanguage;
use App\Models\MediaItem;
use App\Models\Cobenefit;
use App\Models\Block;
use App\Models\BlockLanguage;
use Throwable;
use App\Models\WebsiteObject;
use App\Models\WebsiteObjectLanguage;

class ProjectsSyncService
{
    const BATCH_COUNT = 100;
    const INITIAL_TECH_MAP = [
        'Biomass' => ['new' => 'Biomass ', 'name' => 'Biomass'],
        'Wastewater treatment' => ['new' => 'Wastewater treatment', 'name' => 'Wastewater treatment'],
        'Hydropower' => ['new' => 'Small hydropower', 'name' => 'Small Hydro'],
        'Waste heat recovery' => ['new' => 'Energy Efficiency ', 'name' => ''],
        'Wind power' => ['new' => 'Wind', 'name' => 'Wind'],
        'Landfill gas' => ['new' => 'Waste-to-energy', 'name' => 'Landfill Gas to Energy'],
        'Geothermal' => ['new' => 'Geothermal ', 'name' => 'Geothermal'],
        'Waste gas recovery' => ['new' => 'Waste-to-energy', 'name' => 'Landfill Gas to Energy'],
        'Reforestation' => ['new' => 'Forest Regeneration', 'name' => 'Forest Regeneration'],
        'Cooking stoves' => ['new' => 'Clean Cooking Solutions', 'name' => 'Cooking Stoves'],
        'Afforestation/Reforestation' => ['new' => 'Forest Regeneration', 'name' => 'Forest Regeneration'],
        'REDD+' => ['new' => 'Forest Protection', 'name' => 'Avoided Deforestation'],
        'other' => ['new' => 'Other ', 'name' => ''],
        'Solar' => ['new' => 'Solar ', 'name' => 'Solar panel'],
        'Transport' => ['new' => 'Energy Efficiency ', 'name' => ''],
        'Water filters' => ['new' => 'Clean Water ', 'name' => 'Safe Water'],
        'REDD' => ['new' => 'Forest Protection', 'name' => 'Avoided Deforestation'],
        'Biodiversity conservation instruments' => ['new' => 'Biodiveristy Conservation', 'name' => ''],
        'Mix' => ['new' => 'Other ', 'name' => ''],
    ];

    const MITIGATION_TECH_MAP = [
        'Hydro' => 'Hydropower ',
        'Solar' => 'Solar power',
        'Wind Offshore' => 'Wind power',
        'Wind Onshore' => 'Wind power',
        'Biomass' => 'Waste-to-energy ',
        'Geothermal' => 'Geothermal ',
        'Household Solar' => 'Household solar power',
        'Other RE' => 'Renewable energy generation',
        'Marine Tidal' => 'Hydropower ',
        'Marine Wave' => 'Hydropower ',
        'Ocean Thermal' => 'Ocean Thermal',
        'Energy efficient transformers' => 'Energy efficiency ',
        'High voltage direct current power transmission line' => 'Energy efficiency ',
        'Low resistivity power transmission line' => 'KEEP NAME FROM API',
        'Reactive power compensation in power distribution network' => 'KEEP NAME FROM API',
        'Electrification' => 'KEEP NAME FROM API',
        'Steam system efficiency' => 'KEEP NAME FROM API',
        'Water pumping efficiency improvements' => 'Energy efficiency ',
        'Efficient cookstoves' => 'Clean Cookstoves',
        'Safe water' => 'Safe Water',
        'Building energy efficiency measures' => 'Energy efficiency ',
        'Industry energy efficiency measures' => 'Energy efficiency ',
        'Efficient lamps' => 'Energy efficiency ',
        'E-cookstoves' => 'Clean Cookstoves',
        'Cement/quicklime production improvement' => 'KEEP NAME FROM API',
        'Waste energy recovery' => 'Waste-to-energy',
        'Waste gas recovery' => 'Waste-to-energy',
        'Efficient Charcoal production' => 'Energy efficiency ',
        'SF6 reduction in LCD Manufacturing' => 'KEEP NAME FROM API',
        'PFC gases reduction in semiconductor manufacturing' => 'KEEP NAME FROM API',
        'Fluorinated greenhouse gases reduction in semiconductor manufacturing' => 'KEEP NAME FROM API',
        'Plastic recycling initiatives' => 'Plastic recycling',
        'Biogenic methane utilisation' => 'Waste-to-energy',
        'N2O reduction' => 'KEEP NAME FROM API',
        'Hydrogen production' => 'KEEP NAME FROM API',
        'Efficient transport' => 'Efficient transport',
        'EV' => 'KEEP NAME FROM API',
        'Hydrogen' => 'KEEP NAME FROM API',
        'BRT' => 'KEEP NAME FROM API',
        'Biofuel' => 'Biofuel',
        'Advanced GPS methods' => 'KEEP NAME FROM API',
        'Bunker fuel industry' => 'KEEP NAME FROM API',
        'Vessels fuel efficiency and haul coating' => 'Efficiency in shipping',
        'Marine cargo and customs mitigation' => 'KEEP NAME FROM API',
        'Coal mine methane utilisation' => 'Waste-to-energy',
        'Coal mine methane power generation' => 'Waste-to-energy',
        'Smelter technology upgradation' => 'KEEP NAME FROM API',
        'Electrolytic Cell upgrade and retrofit' => 'KEEP NAME FROM API',
        'Gas recovery' => 'Waste-to-energy',
        'Methane utilisation project' => 'Waste-to-energy',
        'Gas leakage reduction' => 'KEEP NAME FROM API',
        'HFC recovery and decomposition' => 'KEEP NAME FROM API',
        'SF6 recovery and reclamation' => 'KEEP NAME FROM API',
        'Landfill gas (LFG)' => 'Waste-to-energy',
        'Manure management improvement' => 'Waste management',
        'Biogas recovery/Wastewater treatment/Sludge treatment' => 'Waste-to-energy',
        'Household biogas digesters' => 'Waste-to-energy',
        'E-waste' => 'E-waste',
        'Alternative treatment (composting)' => 'Waste-to-energy',
        'Alternative waste treatment (other technologies)' => 'KEEP NAME FROM API',
        'Off Farm - Avoidance' => 'KEEP NAME FROM API',
        'Off Farm - Reduction' => 'KEEP NAME FROM API',
        'Off Farm - Removals' => 'KEEP NAME FROM API',
        'On Farm - Removals' => 'KEEP NAME FROM API',
        'On Farm - Reduction' => 'KEEP NAME FROM API',
        'On Farm - Avoidance' => 'KEEP NAME FROM API',
        'REDD+' => 'Forest Protection',
        'Afforestation, Reforestation Revegetation (ARR)' => 'Afforestation, Reforestation Revegetation (ARR)',
        'Improved Forest Management (IFM)' => 'Improved Forest Management',
        'Agriculture Land Management (ALM)' => 'Sustainble Agriculture',
        'Wetlands Restoration and Conservation (WRC)' => 'Wetlands Restoration and Conservation',
        'Avoided conversion of natural grasslands and shrublands (ACoGS)' => 'Landscape protection',
        'REDD+' => 'Forest Protection',
        'Afforestation, Reforestation Revegetation (ARR) ' => 'Forest Regeneration',
        'Improved Forest Management (IFM)' => 'Improved Forest Management',
        'Agriculture Land Management (ALM)' => 'Sustainable Agriculture',
        'Wetlands Restoration and Conservation (WRC) ' => 'Wetlands Restoration and Conservation',
        'Avoided conversion of natural grasslands and shrublands (ACoGS)' => 'Landscape protection',
        'Carbon Capture and Storage (CCS) ' => 'Carbon Capture and Storage (CCS) ',
        'Saline aquifers and enhanced oil recovery (EOR and EOR+)' => 'Saline aquifers and enhanced oil recovery (EOR and EOR+)',
        'Carbon Capture and Storage (CCS)' => 'Carbon Capture and Storage (CCS)',
        'Bioenergy carbon capture and storage (BECCS)' => 'Bioenergy carbon capture and storage (BECCS)',
        'Direct Air Capture (DAC)' => 'Direct Air Capture (DAC)',
        'Plastic Recycling' => 'Plastic Recycling',
        'Plastic Collection' => 'Plastic Collection'
    ];

    const INITIAL_STANDARD_MAP = [
        'gscer' => ['new' => 'Gold Standard + CDM', 'name' => ''],
        'vcu' => ['new' => 'VCS', 'name' => ''],
        'cer' => ['new' => 'CDM', 'name' => ''],
        'cowboy' => ['new' => 'n/a', 'name' => ''],
        'gsver' => ['new' => 'Gold Standard', 'name' => 'Gold Standard'],
        'ver+' => ['new' => '-', 'name' => ''],
        'CER' => ['new' => 'CDM', 'name' => ''],
        'i-rec' => ['new' => 'i-RECs', 'name' => 'I-REC Standard'],
        'vcusc' => ['new' => 'VSC + Social Carbon', 'name' => ''],
        'vcuccbs' => ['new' => 'VCS + CCBS', 'name' => ''],
        'ccerr' => ['new' => '-', 'name' => ''],
        'cha' => ['new' => '-', 'name' => ''],
        'GS VER' => ['new' => 'Gold Standard', 'name' => 'Gold Standard'],
        'CHA' => ['new' => '-', 'name' => ''],
        'carboncer' => ['new' => '-', 'name' => ''],
        'ccer' => ['new' => '-', 'name' => ''],
        'gs4ggver' => ['new' => 'Gold Standard 4 Global Goals', 'name' => ''],
        'icontec' => ['new' => 'n/a', 'name' => ''],
        'proclima' => ['new' => '-', 'name' => ''],
        'BIOCupo' => ['new' => '-', 'name' => ''],
        'VCS' => ['new' => 'VCS', 'name' => 'Verified Carbon Standard (VCS)'],
        'per' => ['new' => 'Gold Standard', 'name' => 'Gold Standard'],
        'tigr' => ['new' => 'TIGR', 'name' => ''],
        'abu' => ['new' => 'Australian Biodiversity Unit', 'name' => ''],
        'iso' => ['new' => 'ISO', 'name' => 'ISO'],
        'lgc' => ['new' => '-', 'name' => ''],
        'Kyoto-ACCUs' => ['new' => 'ACCU', 'name' => 'ACCU'],
        'corc' => ['new' => 'Puro', 'name' => ''],
    ];

    protected $client;
    protected $languages;
    protected $defaultLang;
    protected $sdgs;
    protected $technologies;
    protected $standards;

    public function __construct()
    {
        if (!config('app.projectsAPI.url') || !config('app.projectsAPI.key')) {
            throw new InvalidConfigurationException('Env config for projects API not set!');
        }

        $this->client = new Client([
            'base_uri' => config('app.projectsAPI.url'),
            'headers' => [
                'X-API-Key' => config('app.projectsAPI.key')
            ]
        ]);

        $this->languages = Localization::getAllLanguages();
        $this->defaultLang = Localization::getDefaultLocale();

        $cobenefits = Cobenefit::whereNotNull('sdg_number')
            ->get();
        $this->sdgs = [];
        foreach ($cobenefits as $cobenefit) {
            $this->sdgs[$cobenefit->sdg_number] = $cobenefit;
        }

        $this->technologies = Technology::get();
        $this->standards = Standard::get();
    }

    public function getActiveProjectsIds() {
        $res = $this->client->request('GET', 'projects/active');
        if ($res->getStatusCode() !== 200) {
            throw new \Exception('Error retrieving project list from API', $res->getReasonPhrase());
        }
        $data = json_decode($res->getBody(), true);

        return array_keys($data['projects']);
    }

    public function getProjects(Array $ids)
    {
        $idQuery = 'no=' . implode('&no=', $ids);

        try {
            $res = $this->client->request('GET', 'projects', [
                'query' => $idQuery
            ]);
        } catch (Exception $e) {
            // If none of the requested projects exists, API replies with an 404 so we have to accept it.
            Log::error('Error getting projects data: ' . $e->getMessage());
            return [];
        }

        if ($res->getStatusCode() !== 200) {
            Log::error('Error getting projects data: ' . $res->getStatusCode() . ' ' . $res->getReasonPhrase());
            return [];
        }
        $data = json_decode($res->getBody(), true);

        return !empty($data) ? $data : [];
    }

    public function syncProjectById($apiId)
    {
        $response = $this->getProjects([$apiId]);

        if (empty($response[0])) {
            echo 'Project not found on API!';
            return false;
        }
        $projectData = $response[0];
        $this->syncProject($apiId, $projectData, true);
    }

    public function syncProjects($force = false, $notifyErrors = false)
    {
        $error = null;
        try {
            $projectIds = $this->getActiveProjectsIds();
            $projectCount = sizeof($projectIds);
            $batches = ceil($projectCount / self::BATCH_COUNT);
            $syncErrors = [];
            Log::info("Found $projectCount active project Ids");

            for ($i = 0; $i < $batches; $i++) {
                Log::info("Processing projects batch $i...");
                $batchIds = array_slice($projectIds, $i * self::BATCH_COUNT, self::BATCH_COUNT);
                $projectsData = $this->getProjects($batchIds);

                foreach ($projectsData as $projectData) {
                    $projectId = $projectData['project_no'];
                    if (!$this->syncProject($projectId, $projectData, $force)) {
                        $syncErrors[] = $projectId;
                    }
                }
            }

            if (!empty($syncErrors)) {
                $error = 'Error syncinc projects: ' . implode(', ', $syncErrors);
            }
        } catch(\Exception $e) {
            Log::error($e);
            Log::error("Error Syncing projects!");
            $error = 'Error with project sync process: ' . $e->getMessage();
        }

        if ($notifyErrors && $error) {
            Mail::to(
                explode(',', config('mail.addresses.notifications'))
            )->send(new ProjectSyncNotification($error));
        }
    }

    protected function syncProject($projectId, $projectData, $forceSync = false)
    {
        DB::beginTransaction();
        try {
            $project = Project::where('spg_id', $projectId)
                ->where('api_sync', true)
                ->first();

            if (!$forceSync && $project && (md5(serialize($projectData)) === $project->import_md5 || $project->is_hero)) {
                Log::info("Project has same import md5 or is hero and force sync is off, skipping... $projectId");
                DB::commit();
                return true;
            }

            if (!$project) {
                $project = $this->createProject($projectId, $projectData);
            }

            $solutionBlock = null;
            $impactBlock = null;

            foreach ($this->languages as $lang) {
                $projectTitle = '';
                $projectStory = '';
                $projectTagline = '';
                $projectSolution = '';
                $projectImpact = '';

                $baseLanguage = substr($lang->id, 0, 2);
                $translatedDetails = $this->getTranslatedData($projectData, $baseLanguage);
                if ($translatedDetails) {
                    $projectTitle = mb_substr($translatedDetails['name'] ? $translatedDetails['name'] : $project->title, 0, 150);
                    $projectStory = ($translatedDetails['story'] ? $translatedDetails['story'] : '');
                    $projectTagline = ($translatedDetails['tagline'] ? $translatedDetails['tagline'] : '');
                    $projectSolution = $translatedDetails['solution'] ? $translatedDetails['solution'] : '';
                    $projectImpact = $translatedDetails['impact'] ? $translatedDetails['impact'] : '';
                }

                $projectLang = $project->languages->findByLocale($lang->id);
                $projectLang->title = $projectTitle;
                $projectLang->content = '<p>' . $projectStory . '</p>';
                $projectLang->objective = '<p>' . $projectTagline . '</p>';
                $projectLang->poa_with_ms_cpa = !empty($projectData['poa_with_ms_cpa']) ? $projectData['poa_with_ms_cpa'] : null;

                if ($projectSolution) {
                    if ($solutionBlock === null) {
                        $solutionBlock = $this->getOrCreateTextBlock($project->id, 'solution');
                    }
                    if (!$project->blocks()->where('block_id', $solutionBlock->id)->where('language_id', $lang->id)->first()) {
                        $project->blocks()->attach($solutionBlock->id, ['position' => 2, 'language_id' => $lang->id]);
                    }
                    $solutionBlockTrans = $solutionBlock->languages->findByLocale($lang->id);
                    $solutionBlockTrans->block_title = trans('frontend.the_solution', [], $lang->id);
                    $solutionBlockTrans->content = '<p>' . $projectSolution . '</p>';
                    $solutionBlockTrans->save();
                }

                if ($projectImpact) {
                    if ($impactBlock === null) {
                        $impactBlock = $this->getOrCreateTextBlock($project->id, 'impact');
                    }
                    if (!$project->blocks()->where('block_id', $impactBlock->id)->where('language_id', $lang->id)->first()) {
                        $project->blocks()->attach($impactBlock->id, ['position' => 5, 'language_id' => $lang->id]);
                    }
                    $impactBlockTrans = $impactBlock->languages->findByLocale($lang->id);
                    $impactBlockTrans->block_title = trans('frontend.the_impact', [], $lang->id);
                    $impactBlockTrans->content = '<p>' . $projectImpact . '</p>';
                    $impactBlockTrans->save();
                }
                $projectLang->save();
            }

            $countryName = null;
            if (!empty($projectData['locations'][0]['country'])) {
                $countryName = $projectData['locations'][0]['country'];
            }

            $countryLang = CountryLanguage::where('language_id', 'en')
                ->where('title', $countryName)
                ->first();

            if (!$countryLang && $countryName) {
                $country = $this->createCountry($countryName);
                $project->country_id = $country->id;
            } else if ($countryLang) {
                $project->country_id = $countryLang->country_id;
            }

            if (!empty($projectData['locations'][0]['longitude'])) {
                $project->location_lng = $projectData['locations'][0]['longitude'];
                $project->location_lat = $projectData['locations'][0]['latitude'];
            }

            $this->syncSdgs($project, $projectData);
            $this->syncTechnology($project, $projectData);

            $standardSpgIds = explode(',', $projectData['standard']);
            $standardSync = [];
            foreach ($standardSpgIds as $standardSpgId) {
                $standardSpgId = trim($standardSpgId);
                if (!$standardSpgId) {
                    continue;
                }
                $standard = null;
                $standardMap = !empty(self::INITIAL_STANDARD_MAP[$standardSpgId]) ? self::INITIAL_STANDARD_MAP[$standardSpgId] : null;
                foreach ($this->standards as $std) {
                    $stdLang = $std->languages->findByLocale('en');
                    if (!empty($standardMap) && ($stdLang->title === $standardMap['name']
                        || $stdLang->title === $standardMap['new'])) {
                        $standard = $std;
                        break;
                    } else if ($stdLang->title === $standardSpgId) {
                        $standard = $std;
                        break;
                    }
                }

                if (!$standard) {
                    $stdName = !empty(self::INITIAL_STANDARD_MAP[$standardSpgId]['new'])
                        ? self::INITIAL_STANDARD_MAP[$standardSpgId]['new']
                        : $standardSpgId;
                    $standard = $this->createStandard($standardSpgId, $stdName);
                    $this->standards->push($standard);
                }
                $standardSync[] = $standard->id;
            }
            $project->standards()->sync($standardSync);

            $this->processMedia($project, $projectData, $projectId);

            $this->syncProjectRegistries($project, $projectData);

            $project->last_sync_at = Carbon::now();
            $project->import_md5 = md5(serialize($projectData));
            $project->save();

            DB::commit();
            Log::info("Project $project->spg_id imported.");
            echo "Project $project->spg_id imported\n";
        } catch (Throwable $e) {
            DB::rollback();
            Log::error($e);
            Log::error("Project $projectId not imported.");
            echo "Project $projectId not imported.";
            return false;
        }
        return true;
    }

    protected function syncTechnology($project, $projectData)
    {
        $mitigationTech = $projectData['mitigation_technology'];
        if (!$mitigationTech) {
            $project->technologies()->sync([]);
            return;
        }
        $mappedTech =
            isset(self::MITIGATION_TECH_MAP[$mitigationTech]) && self::MITIGATION_TECH_MAP[$mitigationTech] !== 'KEEP NAME FROM API'
            ? self::MITIGATION_TECH_MAP[$mitigationTech]
            : $mitigationTech;

        $mappedTech = trim($mappedTech);

        $technology = null;

        foreach ($this->technologies as $tech) {
            $techLang = $tech->languages->findByLocale('en');
            $techMaps = [];
            foreach(preg_split('/\r\n|\r|\n/', $tech->api_id) as $techMap) {
                if (!$techMap) {
                    continue;
                }
                $techMaps[] = strtolower(trim($techMap));
            }
            if (in_array(strtolower($mitigationTech), $techMaps) || strtolower($mappedTech) === strtolower($techLang->title)) {
                $technology = $tech;
                break;
            }
        }

        if (!$technology) {
            $technology = $this->createUpdateTechnology($mappedTech, null);
            $this->technologies->push($technology);
        } else {
            $this->createUpdateTechnology($mappedTech, $technology);
        }

        $project->technologies()->sync([$technology->id]);
    }

    /**
     * Organizes SDGs by ID and groups eventual translations
     *
     * @param array $sdgData
     * @return array
     */
    protected function processSDGData($sdgData)
    {
        $sdgs = [];
        foreach ($sdgData as $sdg) {
            if (preg_match('/^\d+/', $sdg['tag_name'], $matches)) {
                $sdgId = $matches[0];
            } else {
                continue;
            }
            if (!array_key_exists($sdgId, $sdgs)) {
                $sdgs[$sdgId] = [
                    'translations' => []
                ];
            }
            $sdgs[$sdgId]['translations'][$sdg['language']] = $sdg;
        }
        return $sdgs;
    }

    public function syncSdgs($project, $projectData)
    {
        $currentSdgs = [];
        foreach ($project->cobenefits as $cobenefit) {
            $currentSdgs[$cobenefit->cobenefit->sdg_number] = $cobenefit;
        }
        if (!empty($projectData['sdgs'])) {
            $sdgs = $this->processSDGData($projectData['sdgs']);
            foreach ($sdgs as $sdgId => $sdg) {
                $createWo = false;
                if (isset($currentSdgs[$sdgId])) {
                    $projectCobenefit = $currentSdgs[$sdgId];
                    unset($currentSdgs[$sdgId]);
                } else {
                    $projectCobenefit = new ProjectCobenefit();
                    $projectCobenefit->project_id = $project->id;
                    $projectCobenefit->cobenefit_id = $this->sdgs[$sdgId]->id;
                    $projectCobenefit->save();
                    $createWo = true;
                }

                $sdgKpi = [];
                $sdgDescription = [];
                foreach ($sdg['translations'] as $translation) {
                    if (!isset($sdgKpi[$translation['language']])) {
                        $sdgKpi[$translation['language']] = $translation['sdg_kpi'];
                    }
                    if (!isset($sdgDescription[$translation['language']])) {
                        $sdgDescription[$translation['language']] = $translation['sdg_description'];
                    }
                }

                $slugs = [];
                foreach ($this->languages as $lang) {
                    $sdgKpiLang = isset($sdgKpi[$lang->id]) ? $sdgKpi[$lang->id] : '';
                    // Tries to fallback to english, but might not exist
                    if (!$sdgKpiLang && isset($sdgKpi['en'])) {
                        $sdgKpiLang = $sdgKpi['en'];
                    }
                    $sdgDescriptionLang = isset($sdgDescription[$lang->id]) ? $sdgDescription[$lang->id] : '';
                    if (!$sdgDescriptionLang && isset($sdgDescription['en'])) {
                        $sdgDescriptionLang = $sdgDescription['en'];
                    }
                    $projectCobenefitLang = $projectCobenefit->languages->findByLocale($lang->id);
                    if (!$projectCobenefitLang) {
                        $projectCobenefitLang = new ProjectCobenefitLanguage();
                        $projectCobenefitLang->project_cobenefit_id = $projectCobenefit->id;
                        $projectCobenefitLang->language_id = $lang->id;
                    }
                    if (!empty($sdgDescriptionLang)) {
                        $projectCobenefitLang->title = mb_substr($sdgDescriptionLang, 0, 255);
                    } else {
                        $projectCobenefitLang->title = $this->sdgs[$sdgId]->languages->findByLocale($lang->id)->title;
                    }
                    if (!empty($sdgKpiLang)) {
                        $projectCobenefitLang->mesure = mb_substr($sdgKpiLang, 0, 255);
                    } else {
                        $projectCobenefitLang->mesure = '';
                    }

                    $projectCobenefitLang->save();
                    $slugs[$lang->id] = 'proj-cobenefit-i-' . $projectCobenefit->id;
                }

                if ($createWo) {
                    $this->createWo($projectCobenefit->id, 'ProjectCobenefit', 'project_cobenefit', $slugs);
                }
            }

            if ($project->cobenefits()->count()) {
                $sdgPlaceholderBlock = $this->getPlaceholderBlock($project, 'sdgs');
                if ($sdgPlaceholderBlock) {
                    foreach ($this->languages as $lang) {
                        $project->blocks()->attach($sdgPlaceholderBlock->id, ['position' => 1, 'language_id' => $lang->id]);
                    }
                }
            }
        }

        foreach ($currentSdgs as $sdg) {
            $sdg->delete();
            $this->deleteWo($sdg->id, 'project_cobenefit');
        }
    }

    protected function processMedia($project, $projectData, $projectId)
    {
        // gets previously imported images
        $projectImages = $project->projectImages()->pluck('api_name')->toArray();
        $existingNames = [];
        if (!empty($projectData['media'])) {
            $position = 1;
            foreach($projectData['media'] as $imageData) {
                if ($imageData['type'] !== 'Image') {
                    continue;
                }
                if ($imageData['source'] === 'original') {
                    if (in_array($imageData['file_name'], $existingNames)) {
                        // Repeated image coming from the API?
                        continue;
                    }

                    $credits = !empty($imageData['credits']) ? $imageData['credits'] : null;
                    if (in_array($imageData['file_name'], $projectImages)) {
                        // Updates to existing images here!
                        $existingImage = $project->projectImages()->where('api_name', $imageData['file_name'])->first();
                        $existingImage->credits = $credits ? $credits : '';
                        $existingImage->save();
                        $existingNames[] = $imageData['file_name'];
                        $position++;
                        continue;
                    }
                    $mediaItem = $this->importImage($imageData, $projectId);
                    if (!$mediaItem) {
                        continue;
                    }
                    $existingNames[] = $imageData['file_name'];

                    $this->createProjectImage($project, $mediaItem, $imageData['file_name'], $position, $credits);
                    if (!$project->overview_image_id) {
                        $project->overview_image_id = $mediaItem->id;
                    }
                    $position++;
                }
            }

            $galleryPlaceholderBlock = $this->getPlaceholderBlock($project, 'gallery');
            if ($galleryPlaceholderBlock) {
                foreach ($this->languages as $lang) {
                    $project->blocks()->attach($galleryPlaceholderBlock->id, ['position' => 4, 'language_id' => $lang->id]);
                }
            }
        }

        foreach ($project->projectImages as $projecImage) {
            if ($projecImage->api_name && !in_array($projecImage->api_name, $existingNames)) {
                $projecImage->delete();
                $this->deleteWo($projecImage->id, 'project_image');
            }
        }

        if (!empty($projectData['media'])) {
            $videos = [];
            foreach($projectData['media'] as $videoData) {
                if ($videoData['type'] !== 'Video') {
                    continue;
                }
                $videoLang = strtolower($videoData['language']);
                if (empty($videos[$videoLang])) {
                    $videos[$videoLang] = [
                        "url" => $videoData["aq_url"]
                    ];
                }
            }
            if (!empty($videos)) {
                $videoBlock = $this->getOrCreateVideoBlock($project, $projectId);
                if (!$project->blocks()->where('block_id', $videoBlock->id)->first()) {
                    foreach ($this->languages as $lang) {
                        $project->blocks()->attach($videoBlock->id, ['position' => 5, 'language_id' => $lang->id]);
                    }
                }
                foreach ($this->languages as $lang) {
                    $videoBlockLang = $videoBlock->languages->findByLocale($lang->id);
                    if (!empty($videos[$lang->id])) {
                        $videoBlockLang->video = $videos[$lang->id]['url'];
                    } else if (!empty($videos['en']['url'])) {
                        $videoBlockLang->video = $videos['en']['url'];
                    }
                    $videoBlockLang->save();
                }
            }
        }
    }

    protected function getPlaceholderBlock($project, $placeholder)
    {
        foreach ($project->blocks as $block) {
            if ($block->type === "project-ph-$placeholder") {

                return false;
            }
        }
        $wo = WebsiteObject::where('content_type', 'block')
            ->where('slug', "project-ph-$placeholder")
            ->first();

        if ($wo) {
            return $wo->objectable;
        }
        $block = new Block();
        $block->type = "project-ph-$placeholder";
        $block->save();
        $slugs = [];
        foreach ($this->languages as $lang) {
            $blockLang = new BlockLanguage();
            $blockLang->block_id = $block->id;
            $blockLang->language_id = $lang->id;
            $blockLang->title = "Project Placeholder - $placeholder";
            $blockLang->save();
            $slugs[$lang->id] = "project-ph-$placeholder";
        }

        $this->createWo($block->id, 'Block', 'block', $slugs);
        Log::info("Created Placeholder Block project-ph-$placeholder - ID: " . $block->id);

        return $block;
    }

    protected function getOrCreateTextBlock($projectId, $slugCode)
    {
        $wo = WebsiteObject::where('slug', "project-text-i-$slugCode-$projectId")
            ->where('content_type', 'block')
            ->first();

        if ($wo) {
            return $wo->objectable;
        }

        return $this->createTextBlock($projectId, $slugCode);
    }

    protected function createTextBlock($projectId, $slugCode)
    {
        $block = new Block();
        $block->type = 'project-text-1col';
        $block->size = 'large';
        $block->save();
        $slugs = [];
        foreach ($this->languages as $lang) {
            $blockLang = new BlockLanguage();
            $blockLang->block_id = $block->id;
            $blockLang->language_id = $lang->id;
            $blockLang->title = mb_substr('Project Block - ' . $projectId . ' - ' . $slugCode, 0, 150);
            $blockLang->save();
            $slugs[$lang->id] = "project-text-i-$slugCode-$projectId";
        }

        $this->createWo($block->id, 'Block', 'block', $slugs);
        Log::info("Created Text block ($slugCode) for project $projectId");

        return $block;
    }

    protected function createProjectImage($project, $mediaItem, $apiName, $position, $credits = null)
    {
        $projectImage = new ProjectImage();
        $projectImage->project_id = $project->id;
        $projectImage->api_name = $apiName;
        $projectImage->media_item_id = $mediaItem->id;
        $projectImage->credits = $credits ? $credits : '';
        $projectImage->save();
        $slugs = [];
        foreach ($this->languages as $lang) {
            $projectImageLang = new ProjectImageLanguage();
            $projectImageLang->project_image_id = $projectImage->id;
            $projectImageLang->language_id = $lang->id;
            $projectImageLang->title = $apiName;
            $projectImageLang->save();
            $slugs[$lang->id] = 'project-image-' . urlize($apiName);
        }

        $this->createWo($projectImage->id, 'ProjectImage', 'project_image', $slugs, false, $position);
        Log::info("Created Project Image $apiName");

        return $projectImage;
    }

    protected function getOrCreateVideoBlock($project, $projectId, $videoNo = 0)
    {
        $wo = WebsiteObject::where('slug', "project-video-i-$projectId-$videoNo")
            ->where('content_type', 'block')
            ->first();

        if ($wo) {
            return $wo->objectable;
        }

        return $this->createVideoBlock($project, $projectId, $videoNo);
    }

    protected function createVideoBlock($project, $projectId, $videoNo = 0)
    {
        $block = new Block();
        $block->type = 'video';
        $block->save();
        $slugs = [];
        foreach ($this->languages as $lang) {
            $blockLang = new BlockLanguage();
            $blockLang->block_id = $block->id;
            $blockLang->language_id = $lang->id;
            $blockLang->title = 'Video for project ' . $project['name'];
            $blockLang->save();
            $slugs[$lang->id] = "project-video-i-$projectId-$videoNo";
        }

        $this->createWo($block->id, 'Block', 'block', $slugs);
        Log::info("Created Video Block for project $projectId");

        return $block;
    }

    protected function importImage($imageData, $projectId)
    {
        $relatedType = 'project';
        $destinationPath = config('path.internal.media');
        $originalFilename = $imageData['file_name'];
        $parts = explode('.', $originalFilename);
        $extension = strtolower(end($parts));
        if (!$extension) {
            Log::error('Unable to import image: no valid extension: ' . $originalFilename);
            return false;
        }
        $withoutExt = preg_replace('/\\.[^.\\s]{3,4}$/', '', $originalFilename);

        $mediaType = $this->getMediaType($extension);
        if (!$mediaType) {
            Log::error('Unable to import image: media type not suported: ' . $originalFilename);
            return false;
        }

        $newFilename = "project_image_" . $projectId;
        $finalName = $newFilename.'.'.$extension;
        //Checks for colliding filenames
        $i = 1;

        while (Storage::has($destinationPath . '/' . $finalName)) {
            $finalName = $newFilename.'-'.$i.'.'.$extension;
            $i++;
            //for safety
            if ($i > 100) {
                return false;
            }
        }

        // Test env has no image, so we have to get from prd server
        $sourceUrl = str_replace('ab.southpole.com', 'a.southpole.com', $imageData['aq_url']);
        $sourceUrl = preg_replace('/\s/i', '%20', $sourceUrl); // replaces spaces
        try {
            $downloadSuccess = copy($sourceUrl, Storage::path($destinationPath .'/' . $finalName));
        } catch (\Exception $e) {
            Log::error($e);
            Log::error('Unable to import image: could not retrieve image: ' . $originalFilename);
            return false;
        }

        if (!$downloadSuccess) {
            Log::error('Unable to download image: ' . $originalFilename . ' (project: ' . $projectId . ' )');
            return false;
        }
        $mediaItem = new MediaItem(array(
            'filename' => $finalName,
            'original_filename' => $originalFilename,
            'title' => $originalFilename,
            'extension' => $extension,
            'website_id' => 1,
            'related_type' => $relatedType,
            'media_type' => $mediaType,
            'backend_user_id' => null,
        ));

        $mediaItem->save();

        return $mediaItem;
    }

    private function getMediaType($ext)
    {
        $allowedExts = config('backoffice.media.allowedExts');
        foreach ($allowedExts as $type => $allowedExt) {
            if (in_array($ext, $allowedExt)) {
                return $type;
            }
        }
        return false;
    }

    protected function createStandard($standardSpgId, $stdName)
    {
        $standard = new Standard();
        $standard->show_spg = true;
        $standard->api_id = $standardSpgId;
        $standard->save();
        $slugs = [];
        foreach ($this->languages as $lang) {
            $standardLang = new StandardLanguage();
            $standardLang->standard_id = $standard->id;
            $standardLang->language_id = $lang->id;
            $standardLang->title = $stdName;
            $standardLang->save();
            $slugs[$lang->id] = 'standard-' . urlize($standardSpgId);
        }

        $this->createWo($standard->id, 'Standard', 'standard', $slugs);
        Log::info("Created standard $standardSpgId");

        return $standard;
    }

    /**
     * Creates a new tech. If a tech is passed, updates only main instance, not translations
     *
     * @param string $techName
     * @param Technology $technology
     * @return Technology
     */
    protected function createUpdateTechnology($techName, $technology = null)
    {
        $isNew = false;
        if (!$technology) {
            $technology = new Technology();
            $isNew = true;
        }
        $technology->show_spg = true;

        $apiIds = [];
        foreach (self::MITIGATION_TECH_MAP as $apiId => $mappedTech) {
            $mappedTech = trim($mappedTech);
            if (strtolower($mappedTech) == strtolower($techName)) {
                $apiIds[] = strtolower($apiId);
            }
        }

        if (empty($apiIds)) {
            $apiIds[] = strtolower($techName);
        }

        $apiIds = array_unique(array_merge(
            $apiIds,
            ($technology->api_id ? preg_split('/\r\n|\r|\n/', $technology->api_id) : [])
        ));

        $technology->api_id = implode("\n", $apiIds);
        $technology->save();

        if (!$isNew) {
            return $technology;
        }

        $slugs = [];
        foreach ($this->languages as $lang) {
            $technologyLang = new TechnologyLanguage();
            $technologyLang->technology_id = $technology->id;
            $technologyLang->language_id = $lang->id;
            $technologyLang->title = $techName;
            $technologyLang->save();
            $slugs[$lang->id] = 'tech-' . urlize($technologyLang->title);
        }

        $this->createWo($technology->id, 'Technology', 'technology', $slugs);
        Log::info("Created technology $technology->api_id");

        return $technology;
    }

    protected function createCountry($countryName)
    {
        $country = new Country();
        $country->save();
        $slugs = [];
        foreach ($this->languages as $lang) {
            $countryLang = new CountryLanguage();
            $countryLang->country_id = $country->id;
            $countryLang->language_id = $lang->id;
            $countryLang->title = $countryName;
            $countryLang->short_name = $countryLang->title;
            $countryLang->save();
            $slugs[$lang->id] = 'country-' . urlize($countryLang->title);
        }

        $this->createWo($country->id, 'Country', 'country', $slugs);
        Log::info("Created country " . $countryName);

        return $country;
    }

    protected function createProject($projectId, $projectData)
    {
        $project = new Project();
        $project->api_sync = true;
        $project->spg_id = $projectId;
        $project->save();
        $slugs = [];

        foreach ($this->languages as $lang) {
            $dataDetails = $this->getTranslatedData($projectData, $lang->id, true);

            $projectLang = new ProjectLanguage();
            $projectLang->project_id = $project->id;
            $projectLang->language_id = $lang->id;
            if (!$dataDetails || empty($dataDetails['name'])) {
                $projectLang->title = 'Project ' .  $projectId;
            } else {
                $projectLang->title = mb_substr($dataDetails['name'], 0, 150);
            }

            $slugs[$lang->id] = urlize($projectLang->title);
            $projectLang->save();
        }

        $this->createWo($project->id, 'Project', 'project', $slugs, true);

        return $project;
    }

    /**
     * Returns the project data details for the given $language. If $fallback flag is active, returns default language
     * details if found or first language details found
     *
     * @param array $projectData
     * @param string $language
     * @param boolean $fallback
     * @return void
     */
    protected function getTranslatedData($projectData, $language, $fallback = false)
    {
        if (empty($projectData['details'])) {
            return null;
        }
        $defaultDetails = null;
        foreach ($projectData['details'] as $detail) {
            if ($detail['language'] == $language) {
                return $detail;
            }
            if ($this->defaultLang == $detail['language']) {
                $defaultDetails = $detail;
            }
        }
        if ($fallback) {
            return $defaultDetails ? $defaultDetails : $projectData['details'][0];
        }
        return null;
    }

    protected function createWo($id, $objectType, $contentType, $slugs, $isDraft = false, $position = null) {
        $obj = new WebsiteObject();
        $contentConfig = config('backoffice.generic.' . $contentType);

        $baseUri = '';
        if (!empty($contentConfig['baseUri'][1]['en'])) {
            $baseUri = $contentConfig['baseUri'][1]['en'];
        }

        $i = 0;
        $suffix = '';
        $defaultSlug = mb_substr($slugs['en'], 0, 100);
        while(
            WebsiteObject::where('slug', $defaultSlug . $suffix)
                ->where('website_id', 1)
                ->first()
        ) {
            $i++;
            $suffix = "-$i";
        }
        $slug = $defaultSlug . $suffix;

        $obj->website_id = 1;
        $obj->object_id = $id;
        $obj->object_type = $objectType;
        $obj->slug = mb_substr($slug, 0, 150);
        $obj->status = $isDraft ? 'draft' : 'published';
        $obj->content_type = $contentType;
        $obj->uri = $baseUri . '/' . $obj->slug;
        $obj->position = $position;
        $obj->save();
        foreach ($this->languages as $lang) {
            $baseUri = '';
            if (!empty($contentConfig['baseUri'][1][$lang->id])) {
                $baseUri = $contentConfig['baseUri'][1][$lang->id] . '/';
            }

            $langObject = new WebsiteObjectLanguage();
            $langObject->website_object_id = $obj->website_object_id;
            $langObject->wo_language_id = $lang->id;
            $langObject->lang_status = $isDraft ? 'draft' : 'published';

            $i = 0;
            $suffix = '';
            $slug = mb_substr($slugs[$lang->id], 0, 100);
            while(
                WebsiteObjectLanguage::where('lang_slug', $slug . $suffix)
                    ->where('wo_language_id', $lang->id)
                    ->where('wo_website_id', 1)
                    ->first()
            ) {
                $i++;
                $suffix = "-$i";
            }
            $slug = $slug . $suffix;
            $langObject->lang_slug = mb_substr($slug, 0, 150);
            $langObject->lang_uri = $baseUri . $obj->slug;
            $langObject->wo_website_id = $obj->website_id;
            $langObject->save();
        }
    }

    public function syncProjectsRegistries()
    {
        $projectIds = $this->getActiveProjectsIds();
        $projectCount = sizeof($projectIds);
        $batches = ceil($projectCount / self::BATCH_COUNT);
        Log::info("Found $projectCount active project Ids");
        for ($i = 0; $i < $batches; $i++) {
            Log::info("Processing projects batch $i...");
            $batchIds = array_slice($projectIds, $i * self::BATCH_COUNT, self::BATCH_COUNT);
            $projectsData = $this->getProjects($batchIds);

            foreach ($projectsData as $projectData) {
                $projectId = $projectData['project_no'];
                $project = Project::where('spg_id', $projectId)
                    ->where('api_sync', true)
                    ->first();
                if (!$project) {
                    Log::warning("Project not found on CMS: " . $projectId);
                    continue;
                }
                DB::beginTransaction();
                try {
                    $this->syncProjectRegistries($project, $projectData);
                    DB::commit();
                    Log::info("Project registries $projectId imported.");
                } catch (Throwable $e) {
                    DB::rollback();
                    Log::error($e);
                    echo "Project registries $projectId not imported.";
                }
            }
        }
    }

    public function syncProjectsSdgs()
    {
        $projectIds = $this->getActiveProjectsIds();
        $projectCount = sizeof($projectIds);
        $batches = ceil($projectCount / self::BATCH_COUNT);
        Log::info("Found $projectCount active project Ids");
        for ($i = 0; $i < $batches; $i++) {
            Log::info("Processing projects batch $i...");
            $batchIds = array_slice($projectIds, $i * self::BATCH_COUNT, self::BATCH_COUNT);
            $projectsData = $this->getProjects($batchIds);

            foreach ($projectsData as $projectData) {
                $projectId = $projectData['project_no'];
                $project = Project::where('spg_id', $projectId)
                    ->where('api_sync', true)
                    ->first();
                if (!$project) {
                    Log::warning("Project not found on CMS: " . $projectId);
                    continue;
                }
                DB::beginTransaction();
                try {
                    $this->syncSdgs($project, $projectData);
                    DB::commit();
                    Log::info("Project sdgs $projectId imported.");
                } catch (Throwable $e) {
                    DB::rollback();
                    Log::error($e);
                    echo "Project sdgs $projectId not imported.";
                }
            }
        }
    }

    protected function syncProjectRegistries($project, $data)
    {
        $regs = [];
        if (!empty($data['registries'])) {
            Log::info("Found registries: " . sizeof($data['registries']));
            foreach ($data['registries'] as $registry) {
                if (empty($registry['name']) || empty($registry['registry_link'])) {
                    Log::warning("Ignoring registry - No name or link");
                    continue;
                }
                $projectLink = $this->getOrCreateProjectLink($project, $registry);
                $projectLink->url = $registry['registry_link'];
                $projectLink->save();
                $project->load('projectLinks');
                $regs[] = $registry['name'] . '-' . $registry['registry_link'];
            }
        }

        foreach ($project->projectLinks as $projectLink) {
            $projectLinkEn = $projectLink->languages->findByLocale('en');
            $regKey = $projectLinkEn->title . '-' . $projectLink->url;
            if (!in_array($regKey, $regs)) {
                $projectLink->delete();
                $this->deleteWo($projectLink->id, 'project_link');
            } else {
                // first found is removed from array so repeated old registries are removed
                unset($regs[array_search($regKey, $regs)]);
            }
        }
    }

    protected function getOrCreateProjectLink($project, $registry)
    {
        foreach ($project->projectLinks as $projectLink) {
            $projectLinkEn = $projectLink->languages->findByLocale('en');
            if ($projectLinkEn->title == $registry['name'] && $projectLink->url == $registry['registry_link']) {
                return $projectLink;
            }
        }

        return $this->createProjectLink($project, $registry);
    }

    protected function createProjectLink($project, $registry)
    {
        $projecLink = new ProjectLink();
        $projecLink->project_id = $project->id;
        $projecLink->save();
        $slugs = [];
        foreach ($this->languages as $lang) {
            $projecLinkLang = new ProjectLinkLanguage();
            $projecLinkLang->project_link_id = $projecLink->id;
            $projecLinkLang->language_id = $lang->id;
            $projecLinkLang->title = $registry['name'];
            $projecLinkLang->save();
            $slugs[$lang->id] = 'project-link-' . $project->id . '-' . $projecLink->id;
        }

        $this->createWo($projecLink->id, 'ProjectLink', 'project_link', $slugs);
        Log::info("Created Project Link " . $registry['name']);
        return $projecLink;
    }

    protected function deleteWo($id, $contentType) {
        $wo = WebsiteObject::where('content_type', $contentType)
            ->where('object_id', $id)
            ->first();
        if ($wo) {
            $wo->delete();
        }
    }
}
