<?php

/**
 * Core Country Class
 * Class for countries
 * @author Rihards Silins
 * @version 2 23/09/2016
 * @copyright MTC media Ltd
 */

namespace Mtc\Core\Models;

/**
 * Country
 *
 * Select countries from their table
 *
 * @author Rihards Silins
 * @author Vladislavs Ignatjevs <vladislavs.ignatjevs@mtcmedia.co.uk>
 *
 * version 2 18/10/2016
 */

class Country extends \Illuminate\Database\Eloquent\Model
{
    public $timestamps = false;

    /**
     * The table associated with a model
     *
     * @var string
     */
    protected $table = 'countries';

    /**
     *
     * Defining if the country is from Europe by country code or country name
     *
     * @author Vladislavs Ignatjevs <vladislavs.igntatjevs@mtcmedia.co.uk>
     *
     * @param string country
     * @return boolean
     *
     */
    public static function isEurope($country)
    {
        return (
            Country::where('continent_code', '=', 'EU')
                ->where(static function ($query) use ($country): void {
                    $query->where('name', '=', $country)
                        ->orWhere('code', '=', $country);
                })
                ->count() > 0
        );
    }

    /**
     * Static cache of EU countries keyed by uppercase name and code.
     *
     * @var array<string, bool>
     */
    protected static array $euLookup = [];

    /**
     * Determine if the supplied country (name or ISO code) belongs to the EU.
     *
     * @param string $country
     * @return bool
     */
    public static function isEu($country)
    {
        if (empty($country)) {
            return false;
        }

        if (self::$euLookup === []) {
            self::$euLookup = Country::query()
                ->where('is_eu', '=', '1')
                ->get(['name', 'code'])
                ->reduce(static function (array $lookup, $row) {
                    $lookup[self::normalizeCountryKey($row->name)] = true;
                    $lookup[self::normalizeCountryKey($row->code)] = true;

                    return $lookup;
                }, []);
        }

        return self::$euLookup[self::normalizeCountryKey($country)] ?? false;
    }

    private static function normalizeCountryKey(?string $value): string
    {
        if ($value === null) {
            return '';
        }

        $normalized = trim($value);

        if ($normalized === '') {
            return '';
        }

        return mb_strtoupper($normalized, 'UTF-8');
    }

    /**
     *
     * Returns countries that are members of European Union with
     * codes as keys and names as values
     *
     * @author Vladislavs Ignatjevs <vladislavs.igntatjevs@mtcmedia.co.uk>
     *
     * @return string[]
     */
    public static function getEuCountryList()
    {
        return Country::where('is_eu', '=', '1')
            ->orderBy('code', 'asc')
            ->pluck('name', 'code')
            ->all();
    }

    /**
     *
     * Getting list of all countries (full name) with
     * codes as keys and names as values
     *
     * @author Vladislavs Ignatjevs <vladislavs.igntatjevs@mtcmedia.co.uk>
     *
     * @return string[]
     *
     */
    static function getFullCountryList()
    {
        return static::getCountryList();
    }

    /**
     *
     * Getting array of europe countries with codes as keys and names as values
     *
     * @author Vladislavs Ignatjevs <vladislavs.igntatjevs@mtcmedia.co.uk>
     *
     * @return string[]
     *
     */
    static function getEuropeCountryList()
    {
        return static::getCountryList('EU');
    }

    /**
     *
     * Getting array of countries with codes as keys and names as values
     *
     * @author Vladislavs Ignatjevs <vladislavs.igntatjevs@mtcmedia.co.uk>
     *
     * @param string $continent_code
     * @return string[]
     *
     */
    public static function getCountryList($continent_code = '')
    {
        return Country::select('code', 'name')
            ->where(static function ($query) use ($continent_code): void {
                if (! empty($continent_code)) {
                    $query->where('continent_code', '=', $continent_code);
                }
            })
            ->orderBy('order', 'asc')
            ->orderBy('name', 'asc')
            ->pluck('name', 'code')
            ->all();
    }

    /**
     * Get country name from code
     * The most common way countries are retrieved since codes are used as values and names as labels
     * @author Rihards Silins <rihards.silins@mtcmedia.co.uk>
     * @param string $code country code
     * @return string/false $name country name or false if code matches no country
     */
    public static function getNameByCode($code)
    {
        $country = Country::where('code', $code)
            ->first(['name']);
        if ($country == null) {
            return false;
        }

        return $country->name;
    }

    /**
     *
     * Defining if the country uses postcodes in its' mail system
     * example input:
     * $country = 'Germany'
     * or
     * $country = 'DE'
     *
     * @author Jack Donaldson <jack.donaldson@mtcmedia.co.uk>
     *
     * @param string $country
     *
     * @return boolean
     *
     *
     *
     */
    public static function hasPostcodes($country)
    {
        return (
            Country::where('has_postcodes', '=', '1')
                ->where(static function ($query) use ($country): void {
                    $query->where('name', '=', $country)
                        ->orWhere('code', '=', $country);
                })
                ->count() > 0
        );
    }

    /*
     *
     * Retrieves the Regular Expression for the inputted country's postcode
     *
     * If no result is found, the code will assume that the country doesn't require a postcode
     *
     * @param string $country Expected input is the country code : "DR", "FR", "US", etc
     * @return string | null Expected output would be similar to "[0-9]{5}(-\s)[0-9]{4}" if $country = "US"
     *
     */
    public static function getRegex($country): ?string
    {
        // Accept either 2-letter code or full name
        $row = self::query()
            ->where(static function ($q) use ($country): void {
                $q->where('code', $country)
                    ->orWhere('name', $country);
            })
            ->first(['regex']);

        return $row?->regex ?: null;
    }
}
