<?php
/**
 * Class SchemaItem
 *
 * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
 * @contributor Uldis Zvirbulis <uldis.zvirbulis@mtcmedia.co.uk>
 */

namespace Mtc\Plugins\SeoSchema\Classes;

use Baum\Extensions\Eloquent\Collection;
use Baum\Node;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\File;
use Mtc\Plugins\TrustpilotAPI\Classes\TrustpilotAPI;
use Mtc\Shop\Category;
use Mtc\Shop\CustomField;
use Mtc\Shop\CustomFieldSetField;
use Mtc\Shop\Item;
use Mtc\Shop\Item\Size;
use Mtc\Shop\Warning;
use Illuminate\Database\Capsule\Manager as Capsule;

/**
 * Class SchemaItem
 *
 * Seo Schema Item model
 * Used to store seo schema graph fields and values
 *
 * @author Martins Fridenbergs <martins.fridenbergs@mtcmedia.co.uk>
 * @contributor Uldis Zvirbulis <uldis.zvirbulis@mtcmedia.co.uk>
 */
class SchemaItem
{

    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'items_seo_schema';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'item_id',
        'item_type',
        'parent_id',
        'field',
        'value',
        'type'
    ];

    const CUSTOM_FIELD_SET_ID = 2;

    public static $seo_schema_custom_fields = [
        [
            'name' => 'Alternate Name',
            'database_field' => 'schema_alternate_name',
        ],
        [
            'name' => 'Non Proprietary Name',
            'database_field' => 'schema_non_proprietary_name',
        ],
        [
            'name' => 'Same As (Comma separated)',
            'database_field' => 'schema_same_as',
        ],
        [
            'name' => 'Administration Route',
            'database_field' => 'schema_administration_route',
        ],
        [
            'name' => 'Dosage Form',
            'database_field' => 'schema_dosage_form',
        ],
        [
            'name' => 'Active Ingredient',
            'database_field' => 'schema_active_ingredient',
        ],
        [
            'name' => 'Mechanism Of Action',
            'database_field' => 'schema_mechanism_of_action',
        ],
        [
            'name' => 'Related Drug (IDs Comma separated)',
            'database_field' => 'schema_related_drug',
        ],
        [
            'name' => 'Drug Class / Name',
            'database_field' => 'schema_drug_class_name',
        ],
        [
            'name' => 'Drug Class / Same As',
            'database_field' => 'schema_drug_class_same_as',
        ],
        [
            'name' => 'Drug Unit (Comma separated)',
            'database_field' => 'schema_drug_unit',
        ],
        [
            'name' => 'Dose Schedule / Dose Unit',
            'database_field' => 'schema_dose_schedule_unit',
        ],
        [
            'name' => 'Dose Schedule / Dose Value (Comma separated)',
            'database_field' => 'schema_dose_schedule_value',
        ],
        [
            'name' => 'Dose Schedule / Frequency (Comma separated)',
            'database_field' => 'schema_dose_schedule_frequency',
        ],
        [
            'name' => 'Dose Schedule / Target Population',
            'database_field' => 'schema_dose_target_population',
        ],
        [
            'name' => 'Indication / Name',
            'database_field' => 'schema_indication_name',
        ],
        [
            'name' => 'Indication / Same As',
            'database_field' => 'schema_indication_same_as',
        ],
        [
            'name' => 'Indication / Alternate Name',
            'database_field' => 'schema_indication_alternate_name',
        ],
        [
            'name' => 'Adverse Outcome (Comma separated)',
            'database_field' => 'schema_adverse_outcome',
        ],
        [
            'name' => 'Serious Adverse Outcome (Comma separated)',
            'database_field' => 'schema_serious_adverse_outcome',
        ],
        [
            'name' => 'Contraindication (Comma separated)',
            'database_field' => 'schema_contraindication',
        ],
        [
            'name' => 'Available Strengths (value:unit,value:unit)',
            'database_field' => 'schema_available_strengths',
        ],
        [
            'name' => 'Label Details',
            'database_field' => 'schema_label_details',
        ],
        [
            'name' => 'Prescribing Info',
            'database_field' => 'schema_prescribing_info',
        ],
        [
            'name' => 'Brands (name|URL,name|URL)',
            'database_field' => 'schema_brands',
        ],
        [
            'name' => 'Warnings (text1|text2|text3)',
            'database_field' => 'schema_warnings',
        ],
        [
            'name' => 'Manufacturer Name',
            'database_field' => 'schema_manufacturer_name',
        ],
        [
            'name' => 'Manufacturer URL',
            'database_field' => 'schema_manufacturer_url',
        ],
    ];

    /**
     * Boot the model
     */
    public static function boot() : void
    {
        //parent::boot();

        self::addGlobalScope(function ($query) {
            return $query->orderBy('lft');
        });

        self::saved(function($schema_item) {
            // ignore for one-off-script
            if ($_SERVER['SCRIPT_FILENAME'] !== 'populate_item_schemas.php') {
                // update item schema cache
                $item = Item::query()->find($schema_item->item_id);
                if ($item) {
                    $item->seo_schema_cache = json_encode(self::renderTree($schema_item->item_id), JSON_UNESCAPED_SLASHES);
                    $item->save();
                }
            }
        });

        self::deleted(function($schema_item) {
            // ignore for one-off-script
            if ($_SERVER['SCRIPT_FILENAME'] !== 'populate_item_schemas.php') {
                // update item schema cache
                $item = Item::query()->find($schema_item->item_id);
                if ($item) {
                    $item->seo_schema_cache = json_encode(self::renderTree($schema_item->item_id), JSON_UNESCAPED_SLASHES);
                    $item->save();
                }
            }
        });

    }

    /**
     * Generate the full tree for Knowledge graph
     *
     * @param int $item_id root_ele
     * @return \Illuminate\Support\Collection
     */
    public static function renderTree($item_id)
    {
        return self::roots()
            ->where('item_type', Item::class)
            ->where('item_id', $item_id)
            ->get()
            ->keyBy('field')
            ->map(function (self $leaf) {
                return $leaf->processChildren();
            });
    }

    /**
     * Build up a recursive structure for Knowledge graph
     * This function recursively calls itself if nescessary
     *
     * @return mixed
     */
    public function processChildren()
    {
        if ($this->type === 'text') {
            return $this->value;
        } elseif ($this->type === 'list') {
            return $this->children
                ->keyBy('field')
                ->map(function (self $leaf) {
                    return $leaf->processChildren();
                });
        } else {
            return $this->children
                ->keyBy('field')
                ->map(function (self $leaf) {
                    return $leaf->processChildren();
                });
        }
    }

    /**
     * Saves the SEO Schema cache as JSON
     *
     * @param $item
     */
    public static function buildCache($item)
    {
        $schemas = [];
        $schemas[] = self::setItemInfo($item);

        if ($item->product_type === 'doctor') {
            $schemas[] = self::setDoctorItemInfo($item);
        }
        if ($item_el = Item::query()->find($item->id)) {
            $item_el->seo_schema_cache = json_encode($schemas, JSON_UNESCAPED_SLASHES);
            $item_el->save();
        }
    }

    /**
     * Builds the SEO Schema tree
     *
     * @param Item $item
     */
    public static function build(Item $item)
    {
        //clear the old values if they exist
        self::query()
            ->where('item_type', Item::class)
            ->where('item_id', $item->id)
            ->delete();
        // Set the item information
        self::buildSchema($item->id, self::setItemInfo($item));
        if ($item->product_type === 'doctor') {
            self::buildSchema($item->id, self::setDoctorItemInfo($item));
        }
    }

    /**
     * Generate information about sizes on product
     * and store the information as needed for seo schema
     *
     * @param \Item $item
     * @return array
     */
    public static function setItemInfo($item)
    {
        $item_image = '';
        if ($item->images && $item->image_default) {
            $item_image = SITE_URL . '/uploads/images/products/thumbs/' . $item->images[$item->image_default]['name'];
        }

        /** @var Item $item_el */
        $item_el = Item::query()->find($item->id);

        if ($item->product_type === 'doctor') {
            $structure = [
                '@context' => 'http://schema.org',
                '@id' => '#ProductDrug',
                '@type' => [
                    'Product',
                    'Drug',
                ],
                'name' => $item->name,
                'alternateName' => $item->custom['schema_alternate_name'] ?? '',
                'nonProprietaryName' => $item->custom['schema_non_proprietary_name'] ?? '',
                'description' => [
                    !empty($item->custom['schema_description']) ?
                        str_replace(["\r\n"], '. ', strip_tags($item->custom['schema_description'])) :
                        $item->name
                ],
                'sku' => $item->epos_code,
                'image' => $item_image,
                'mainEntityOfPage' => [
                    '@id' => SITE_URL . $item->url,
                    '@type' => 'ItemPage',
                    'publisher' => [
                        '@id' => SITE_URL
                    ],
                    'datePublished' => str_replace(' ', 'T', $item->created_at) . '+00:00',
                    'dateModified' => str_replace(' ', 'T', $item->updated_at) . '+00:00',
                ],
                'url' => SITE_URL . $item->url,
                'prescriptionStatus' => 'http://schema.org/PrescriptionOnly',
                'legalStatus' => [
                    '@type' => 'DrugLegalStatus',
                    'name' => 'prescription only',
                    'applicableLocation' => [
                        '@type' => 'AdministrativeArea',
                        'name' => 'EU',
                        'sameAs' => 'https://en.wikipedia.org/wiki/European_Union'
                    ]
                ],
                'offers' => self::setSizeInfo($item),

            ];
            if (!empty($item->custom['schema_same_as'])) {
                $structure['sameAs'] = [];
                $same_as_parts = explode(',', $item->custom['schema_same_as']);
                foreach ($same_as_parts as $same_as_part) {
                    $structure['sameAs'][] = $same_as_part;
                }
            }
            if (!empty($item->custom['schema_administration_route'])) {
                $structure['administrationRoute'] = $item->custom['schema_administration_route'];
            }
            if (!empty($item->custom['schema_dosage_form'])) {
                $structure['dosageForm'] = $item->custom['schema_dosage_form'];
            }
            if (!empty($item->custom['schema_active_ingredient'])) {
                $structure['activeIngredient'] = $item->custom['schema_active_ingredient'];
            }
            if (!empty($item->custom['schema_mechanism_of_action'])) {
                $structure['mechanismOfAction'] = $item->custom['schema_mechanism_of_action'];
            }
            if (!empty($item->custom['schema_label_details'])) {
                $structure['labelDetails'] = $item->custom['schema_label_details'];
            }
            if (!empty($item->custom['schema_prescribing_info'])) {
                $structure['prescribingInfo'] = $item->custom['schema_prescribing_info'];
            }
            if (!empty($item->custom['schema_warnings'])) {
                $structure['warning'] = explode('|', $item->custom['schema_warnings']);
            }
            if (!empty($item->custom['schema_related_drug'])) {
                $item_ids = explode(',', $item->custom['schema_related_drug']);
                $item_ids = array_map('trim', $item_ids);
                $structure['relatedDrug'] = [];
                foreach ($item_ids as $item_id) {
                    $related_item = new \Item($item_id);
                    $structure['relatedDrug'][] = [
                        '@type' => 'Drug',
                        'name' => $related_item->name,
                        'url' => SITE_URL . $related_item->url,
                    ];
                }
            }
            if (!empty($item->custom['schema_drug_class_name'])) {
                $structure['drugClass'] = [
                    '@type' => 'DrugClass',
                    'name' => $item->custom['schema_drug_class_name'],
                    'sameAs' => $item->custom['schema_drug_class_same_as'],
                ];
            }
            if (!empty($item->custom['schema_drug_unit'])) {
                $structure['drugUnit'] = explode(',', $item->custom['schema_drug_unit']);
            }
            if (!empty($item->custom['schema_dose_schedule_unit'])) {
                $structure['doseSchedule'] = [
                    '@type' => 'DoseSchedule',
                    'doseUnit' => $item->custom['schema_dose_schedule_unit'],
                    'doseValue' => explode(',', $item->custom['schema_dose_schedule_value']),
                    'frequency' => explode(',', $item->custom['schema_dose_schedule_frequency']),
                    'targetPopulation' => $item->custom['schema_target_population'],
                ];
            }
            if (!empty($item->custom['schema_available_strengths'])) {
                $structure['availableStrength'] = [];
                $available_strengths = explode(',', $item->custom['schema_available_strengths']);
                foreach ($available_strengths as $available_strength) {
                    $strength = explode(':', $available_strength);
                    $structure['availableStrength'][] = [
                        '@type' => 'DrugStrength',
                        '@id' => '#' . $item->name . $strength[0] . $strength[1],
                        'strengthValue' => $strength[0],
                        'strengthUnit' => $strength[1],
                    ];
                }
            }

            if (!empty($item->custom['schema_brands'])) {
                $structure['brand'] = [];
                $brands = explode(',', $item->custom['schema_brands']);
                foreach ($brands as $brand) {
                    $brand_data = explode('|', $brand);
                    $structure['brand'][] = [
                        '@type' => 'Organization',
                        '@id' => '#' . $brand_data[0],
                        'name' => $brand_data[0],
                        'url' => $brand_data[1],
                    ];
                }
            }

        } else {
            $structure = [
                '@context' => 'http://schema.org',
                '@id' => '#ProductDrug',
                '@type' => [
                    'Product',
                ],
                'name' => $item->name,
                'url' => SITE_URL . $item->url,
                'description' => [
                    !empty($item->custom['schema_description']) ?
                        str_replace(["\r\n"], '. ', strip_tags($item->custom['schema_description'])) :
                        $item->name
                ],
                'sku' => $item->epos_code,
                'image' => $item_image,
                'mainEntityOfPage' => [
                    '@id' => SITE_URL . $item->url,
                    '@type' => 'ItemPage',
                    'publisher' => [
                        '@id' => SITE_URL
                    ],
                ],
                'offers' => self::setSizeInfo($item)
            ];

            if (!empty($item->brand_name)) {
                $structure['brand'] = [
                    [
                        '@type' => 'Organization',
                        '@id' => '#' . $item->brand_name,
                        'name' => $item->brand_name,
                    ],
                ];
            }
        }

        $reviews = collect(json_decode(TrustpilotAPI::getProductReviews($item->id, ''))->productReviews ?? '[]');
        if ($reviews->count() > 0) {
            $structure['aggregateRating'] = [
                '@type' => 'AggregateRating',
                'ratingValue' => number_format($reviews->sum('stars') / $reviews->count(), 2, '.', ''),
                'bestRating' => '5',
                'worstRating' => '1',
                'reviewCount' => $reviews->count(),
                'url' => SITE_URL . $item->url,
            ];
        }

        return $structure;
    }

    /**
     * Generate information about sizes on product
     * and store the information as needed for seo schema
     *
     * @param \Item $item
     * @return array
     */
    public static function setSizeInfo($item)
    {
        /** @var \Illuminate\Support\Collection $sizes */
        $sizes = Size::ofItem($item->id)->get();
        if ($sizes->count() < 2) {
            if ($sizes->count() === 1) {
                // For items with one size, set the size price
                $size = $sizes->first();
                $price = $size->sale_price == 0 ? $size->price : $size->sale_price;
            } else {
                // For items with no sizes set the product price
                $price = $item->sale_price == 0 ? $item->price : $item->sale_price;
            }
            return [
                [
                    '@type' => 'Offer',
                    'availability' => 'http://schema.org/InStock',
                    'price' => number_format($price, 2),
                    'priceCurrency' => 'GBP',
                    'priceValidUntil' => Carbon::now()->addYear()->format('Y-m-d'),
                    'url' => SITE_URL . $item->url,
                ]
            ];
        }

        $min_price = $sizes->min('price');
        $max_price = $sizes->max('price');

        $item_image = '';
        if (count((array)$item->images)) {
            $item_image = SITE_URL . '/uploads/images/products/thumbs/' . $item->images[$item->image_default]['name'];
        }

        $offers =  [
            [
                '@type' => 'AggregateOffer',
                'lowPrice' => number_format($min_price, 2),
                'highPrice' => number_format($max_price, 2),
                'offerCount' => $sizes->count(),
                'priceCurrency' => 'GBP',
            ]
        ];

        foreach ($sizes as $size) {
            $item_offered_type = ['Product'];
            if ($item->product_type === 'doctor') {
                $item_offered_type[] = 'Drug';
            }

            $item_offered = [
                '@type' => $item_offered_type,
                'name' => $item->name . ' ' . $size->size,
                'image' => $item_image,
            ];
            if ($item->product_type === 'doctor') {
                $item_offered['gtin13'] = $item->custom['gtin_13'] ?? '';
                if (!empty($item->custom['schema_manufacturer_name'])) {
                    $item_offered['manufacturer'] = [
                        '@type' => 'Organization',
                        'name' => $item->custom['schema_manufacturer_name'],
                        'url' => $item->custom['schema_manufacturer_url'],
                    ];
                }
                $item_offered['availableStrength'] = [
                    '@id' => '#' . $size->strength,
                ];
            }

            $offer = [
                '@type' => 'Offer',
                'url' => SITE_URL . $item->url,
                'availability' => 'http://schema.org/InStock',
                'itemCondition' => 'http://schema.org/NewCondition',
                'priceCurrency' => 'GBP',
                'price' => number_format($size->price, 2),
                'priceValidUntil' => Carbon::now()->addYear()->format('Y-m-d'),
                'seller' => [
                    '@id' => SITE_URL,
                ],
                'itemOffered' => $item_offered,
                'priceSpecification' => [
                    [
                        '@type' => 'DeliveryChargeSpecification',
                        'name' => \DeliveryMethod::query()->find(1)->name,
                        'eligibleRegion' => 'GB',
                        'price' => number_format(\DeliveryMethod::query()->find(1)->cost, 2),
                        'priceCurrency' => 'GBP'
                    ]
                ],
            ];
            $offers[] = $offer;
        }

        return $offers;
    }


    /**
     * SEO Schema for Medical Therapy
     *
     * @param \Item $item
     * @return array
     */
    public static function setDoctorItemInfo($item)
    {
        $item_image = '';
        if ($item->images && $item->image_default) {
            $item_image = SITE_URL . '/uploads/images/products/thumbs/' . $item->images[$item->image_default]['name'];
        }

        /** @var Item $item_el */
        $item_el = Item::query()->find($item->id);

        $category = Category::query()
            ->where('is_online_doctor', 1)
            ->where('sub_id', '!=', 0)
            ->whereHas('items', function ($query) use ($item) {
                $query->where('items.id', $item->id);
            })->first();
        $indication = [
            '@type' => 'TreatmentIndication',
            'url' => SITE_URL . '/' . \Mtc\Shop\Category::getOnlineDoctorRoutePrefix() . '/' . ($category->slug ?? '') . '/',
        ];
        if (!empty($item->custom['schema_indication_name'])) {
            $indication['name'] = $item->custom['schema_indication_name'];
        }
        if (!empty($item->custom['schema_indication_same_as'])) {
            $indication['sameAs'] = $item->custom['schema_indication_same_as'];
        }
        if (!empty($item->custom['schema_indication_alternate_name'])) {
            $indication['alternateName'] = $item->custom['schema_indication_alternate_name'];
        }

        $structure = [
            '@context' => 'http://schema.org',
            '@type' => 'MedicalTherapy',
            'drug' => [
                '@type' => 'Drug',
                '@id' => SITE_URL . $item->url . '#ProductDrug',
            ],
            'indication' => $indication,
        ];

        if (!empty($item->custom['schema_adverse_outcome'])) {
            $structure['adverseOutcome'] = [];
            $adverse_outcomes = explode(',', $item->custom['schema_adverse_outcome']);
            foreach ($adverse_outcomes as $adverse_outcome) {
                $structure['adverseOutcome'][] = [
                    '@type' => 'MedicalSymptom',
                    'name' => $adverse_outcome,
                ];
            }
        }

        if (!empty($item->custom['schema_serious_adverse_outcome'])) {
            $structure['seriousAdverseOutcome'] = [];
            $serious_adverse_outcomes = explode(',', $item->custom['schema_serious_adverse_outcome']);
            foreach ($serious_adverse_outcomes as $serious_adverse_outcome) {
                $structure['seriousAdverseOutcome'][] = [
                    '@type' => 'MedicalSymptom',
                    'name' => $serious_adverse_outcome,
                ];
            }
        }

        if (!empty($item->custom['schema_contraindication'])) {
            $structure['contraindication'] = [];
            $contraindications = explode(',', $item->custom['schema_contraindication']);
            foreach ($contraindications as $contraindication) {
                $structure['contraindication'][] = [
                    '@type' => 'MedicalContraindication',
                    'name' => $contraindication,
                ];
            }
        }

        return $structure;
    }


    /**
     * Generate schema tree structure in database based on given $tree structure
     *
     * @param $item_id
     * @param $tree
     */
    protected static function buildSchema($item_id, $tree, $parent_id = null)
    {
        collect($tree)->each(function ($leaf, $key) use ($item_id, $parent_id) {
            // We need to find if this is a text entry or a list/object
            if (is_array($leaf)) {
                // Easiest way how to distinguish list vs object is finding a sum of the array keys
                // If it is an int value its an unnamed list
                // For object the sum will be 0 as you can't sum strings
                $type = array_sum(array_keys($leaf)) ? 'list' : 'object';

                $parent = SchemaItem::query()->create([
                    'item_id' => $item_id,
                    'item_type' => Item::class,
                    'parent_id' => $parent_id,
                    'field' => $key,
                    'type' => $type
                ]);
                self::buildSchema($item_id, $leaf, $parent->id);
            } else {
                SchemaItem::query()->create([
                    'item_id' => $item_id,
                    'item_type' => Item::class,
                    'parent_id' => $parent_id,
                    'field' => $key,
                    'value' => $leaf,
                    'type' => 'text'
                ]);
            }
        });
    }

    public static function installCustomFields()
    {
        $schema_custom_fields = self::$seo_schema_custom_fields;

        $schema = Capsule::connection()->getSchemaBuilder();
        foreach ($schema_custom_fields as $schema_custom_field) {
            if (CustomField::query()->where('database_field', $schema_custom_field['database_field'])->count() === 0) {
                $sort = CustomField::query()->max('sort') + 1;
                $custom_field_id = CustomField::query()->insertGetId([
                    'name' => $schema_custom_field['name'],
                    'type' => 'text',
                    'database_field' => $schema_custom_field['database_field'],
                    'sort' => $sort,
                ]);
                CustomFieldSetField::query()->create([
                    'set_id' => self::CUSTOM_FIELD_SET_ID,
                    'custom_field_id' => $custom_field_id,
                    'items' => 1,
                    'variations' => 0,
                ]);
            }
            if (!$schema->hasColumn('items_custom', $schema_custom_field['database_field'])) {
                $schema->table('items_custom', function (Blueprint $table) use ($schema_custom_field) {
                    $table->string($schema_custom_field['database_field'], 500);
                });
            }
            if (!$schema->hasColumn('items_sizes_custom', $schema_custom_field['database_field'])) {
                $schema->table('items_sizes_custom', function (Blueprint $table) use ($schema_custom_field) {
                    $table->string($schema_custom_field['database_field'], 500);
                });
            }
        }
    }
}
