<?php

namespace Velis\Test;

use DateTime;
use DateTimeZone;
use PHPUnit\Framework\TestCase;
use Velis\App;
use Velis\Exception;
use Velis\Lang;
use Velis\Output;

/**
 * @author Jan Małysiak <jan.malysiak@velis.pl>
 */
class OutputTest extends TestCase
{
    public static function setUpBeforeClass(): void
    {
        App::$config->settings->disableNumberFormatter = false;
    }

    /**
     * @param string $locale
     * @param float  $money
     * @param bool   $includeZero
     * @param string $currency
     * @param string $expectedResult
     *
     * @dataProvider provideDataForFormatMoneyMethod
     */
    public function testFormatMoney($locale, $money, $includeZero, $currency, $expectedResult)
    {
        setlocale(LC_ALL, $locale);

        $actualResult = Output::formatMoney($money, $includeZero, $currency);

        $this->assertEquals($expectedResult, $actualResult);
    }

    /**
     * @return array
     */
    public function provideDataForFormatMoneyMethod()
    {
        return [
            ['pl_PL', 1.23, false, 'PLN', '1,23 zł'],
            ['en_US', 1.23, false, 'PLN', 'PLN 1.23'],
            ['pl_PL', 1000, false, 'USD', '1 000,00 USD'],
            ['en_US', 1000, false, 'USD', '$1,000.00'],
            ['pl_PL', 0, false, 'PLN', ''],
            ['pl_PL', 0, true, 'PLN', '0,00 zł'],
            ['pl_PL', 1.23, false, null, '1,23'],
            ['en_US', 1.23, false, null, '1.23'],
            ['pl_PL', 1.23, false, 'zł', '1,23 zł'],
            ['en_US', 1.23, false, 'zł', '1.23 zł'],
            ['LC_CTYPE=en_US.UTF-8;LC_NUMERIC=en_US;LC_TIME=C;LC_COLLATE=C;LC_MONETARY=C;LC_MESSAGES=C;LC_PAPER=C;LC_NAME=C;LC_ADDRESS=C;LC_TELEPHONE=C;LC_MEASUREMENT=C;LC_IDENTIFICATION=C', 1.23, false, 'USD', '$1.23'],
        ];
    }

    /**
     * @param string $locale
     * @param string $date
     * @param bool   $includeTime
     * @param bool   $includeSeconds
     * @param string $expectedResult
     *
     * @dataProvider provideDataForFormatDateMethod
     */
    public function testFormatDate($locale, $date, $includeTime, $includeSeconds, $expectedResult)
    {
        setlocale(LC_ALL, $locale);

        $actualResult = Output::formatDate($date, $includeTime, $includeSeconds);

        $this->assertEquals($expectedResult, $actualResult);
    }

    /**
     * @return array
     */
    public function provideDataForFormatDateMethod()
    {
        return [
            ['pl_PL', '2019-03-26 15:04:09', false, false, '26.03.2019'],
            ['pl_PL', '2019-03-26 15:04:09', true, false, '26.03.2019 15:04'],
            ['pl_PL', '2019-03-26 15:04:09', true, true, '26.03.2019 15:04:09'],
            ['en_US', '2019-03-26 15:04:09', false, false, '03/26/2019'],
            ['en_US', '2019-03-26 15:04:09', true, false, '03/26/2019 03:04 PM'],
            ['en_US', '2019-03-26 15:04:09', true, true, '03/26/2019 03:04:09 PM'],
            ['en_GB', '2019-03-26 15:04:09', false, false, '26/03/19'],
            ['en_GB', '2019-03-26 15:04:09', true, false, '26/03/19 15:04'],
            ['en_GB', '2019-03-26 15:04:09', true, true, '26/03/19 15:04:09'],
        ];
    }

    /**
     * @param string $locale
     * @param string $dateString
     * @param string $expectedResult
     * @param bool   $useDeclension
     *
     * @dataProvider provideDataForGetMonthMethod
     */
    public function testGetMonth($locale, $dateString, $expectedResult, $useDeclension = false)
    {
        Lang::switchLanguage(substr($locale, 0, 2), false);

        $actualResult = Output::getMonth($dateString, $useDeclension);

        $this->assertEquals($expectedResult, $actualResult);
    }

    /**
     * @return array
     */
    public function provideDataForGetMonthMethod()
    {
        return [
            ['en_US', '2019-09-27', 'September'],
            ['en_US', '2019-10-01', 'October'],
            ['pl_PL', '2019-09-27', 'wrzesień'],
            ['pl_PL', '2019-09-27', 'września', true],
            ['pl_PL', '2019-10-01', 'październik'],
            ['pl_PL', '2019-10-01', 'października', true],
            ['hu_HU', '2019-09-27', 'szeptember'],
            ['hu_HU', '2019-10-01', 'október'],
        ];
    }

    /**
     * @param string $locale
     * @param string $dateString
     * @param $expectedResult
     *
     * @dataProvider provideDataForFormatDateInWordsMethod
     */
    public function testFormatDateInWords($locale, $dateString, $expectedResult)
    {
        setlocale(LC_ALL, $locale);
        Lang::switchLanguage(substr($locale, 0, 2), false);

        $actualResult = Output::formatDateInWords($dateString);

        $this->assertEquals($expectedResult, $actualResult);
    }

    /**
     * @return array
     */
    public function provideDataForFormatDateInWordsMethod()
    {
        return [
            ['pl_PL', '2019-09-27', '27 września 2019'],
            ['en_GB', '2019-09-27', '27th September 2019'],
            ['en_US', '2019-09-27', 'September 27th, 2019'],
            ['de_DE', '2019-09-27', '27. September 2019'],
            ['hu_HU', '2019-09-27', '2019. szeptember 27.'],
            ['es_ES', '2019-09-27', '27 de septiembre de 2019'],
            ['fr_FR', '2019-09-27', '27 septembre 2019'],
        ];
    }

    /**
     * @param string $locale
     * @param string $input
     * @param bool $includeSeconds
     * @param string $expectedResult
     *
     * @dataProvider provideDataForFormatHourMethod
     */
    public function testFormatHour($locale, $input, $includeSeconds, $expectedResult)
    {
        setlocale(LC_ALL, $locale);

        $actualResult = Output::formatHour($input, $includeSeconds);

        $this->assertEquals($expectedResult, $actualResult);
    }

    public function provideDataForFormatHourMethod()
    {
        return [
            ['pl_PL', '2020-01-01 13:30:00', true, '13:30:00'],
            ['pl_PL', '2020-01-01 13:30:00', false, '13:30'],
            ['en_US', '2020-01-01 13:30:00', true, '01:30:00 PM'],
            ['en_US', '2020-01-01 13:30:00', false, '01:30 PM'],
        ];
    }

    /**
     * @param $dataToEncode
     * @param $options
     * @param $expectedResult
     *
     * @dataProvider provideDataForJsonEncodeMethod
     */
    public function testJsonEncode($dataToEncode, $options, $expectedResult)
    {
        $actualResult = Output::jsonEncode($dataToEncode, $options);

        $this->assertEquals($expectedResult, $actualResult);
    }

    public function provideDataForJsonEncodeMethod()
    {
        $associativeArray = [
            'building_id' => 1,
            'domain' => 'http://foo.bar',
        ];

        return [
            [$associativeArray, 0, '{"building_id":1,"domain":"http:\/\/foo.bar"}'],
            [$associativeArray, JSON_UNESCAPED_SLASHES, '{"building_id":1,"domain":"http://foo.bar"}'],
        ];
    }

    /**
     * @param string $langId
     * @param int $time
     * @param string $format
     * @param string $expectedOutput
     *
     * @dataProvider provideDataForFormatTimeMethod
     */
    public function testFormatTime($langId, $time, $format, $expectedOutput)
    {
        Lang::switchLanguage($langId, false);

        $actualOutput = Output::formatTime($time, $format);

        $this->assertEquals($expectedOutput, $actualOutput);
    }

    public function provideDataForFormatTimeMethod()
    {
        return [
            ['pl', 1, null, '0 min.'],
            ['pl', 60, null, '1 min.'],
            ['pl', 100, null, '1 min.'],
            ['pl', 120, null, '2 min.'],
            ['pl', 3600, null, '1 godz. 0 min.'],
            ['pl', 3950, null, '1 godz. 5 min.'],
            ['pl', 5400, null, '1 godz. 30 min.'],
            ['pl', 172800, null, '48 godz. 0 min.'],
            ['pl', -60, null, '1 min.'],
            ['en', 3600, null, '1 h 0 min.'],
            ['pl', 5, '%h:%I', '0:00'],
            ['pl', 60, '%h:%I', '0:01'],
            ['pl', 1800, '%h:%I', '0:30'],
            ['pl', 172800, '%h:%I', '48:00'],
            ['pl', 5, '%i:%S', '0:05'],
            ['pl', -60, '%h:%I', '0:01'],
            ['pl', 3950, '%h:%I h', '1:05 h'],
        ];
    }

    /**
     * @param string $locale
     * @param string $numericString
     * @param float|false $expectedResult
     *
     * @dataProvider provideDataForParseFloatMethod
     */
    public function testParseFloat(string $locale, string $numericString, $expectedResult)
    {
        setlocale(LC_ALL, $locale);

        $actualResult = Output::parseFloat($numericString);

        $this->assertEquals($expectedResult, $actualResult);
    }

    public function provideDataForParseFloatMethod()
    {
        return [
            ['pl_PL', '6,25', 6.25],
            ['pl_PL', '6.25', false],
            ['en_US', '6.25', 6.25],
            ['en_US', '6,25', false],
        ];
    }

    /**
     * @param $input
     * @param $timeZone
     * @param string $expectedOutput
     *
     * @dataProvider provideDataForFormatIsoDateTimeMethod
     */
    public function testFormatIsoDateTime($input, $timeZone, string $expectedOutput)
    {
        $actualOutput = Output::formatIsoDateTime($input, $timeZone);

        $this->assertEquals($expectedOutput, $actualOutput);
    }

    public function provideDataForFormatIsoDateTimeMethod()
    {
        return [
            ['2021-04-08 12:30:45', null, '2021-04-08T12:30:45+02:00'],
            [new DateTime('2021-04-08 13:15:20'), null, '2021-04-08T13:15:20+02:00'],
            ['2021-04-08 13:40:50', new DateTimeZone('Europe/London'), '2021-04-08T12:40:50+01:00'],
            [new DateTime('2021-04-08 14:05:00'), new DateTimeZone('UTC'), '2021-04-08T12:05:00+00:00'],
            ['2021-04-08T14:15:35+02:00', null, '2021-04-08T14:15:35+02:00'],
        ];
    }

    /**
     * @param string $input
     * @param string $expectedOutput
     *
     * @dataProvider provideDataForStripPolishCharsMethod
     */
    public function testStripPolishChars(string $input, string $expectedOutput)
    {
        $actualOutput = Output::stripPolishChars($input);

        $this->assertEquals($expectedOutput, $actualOutput);
    }

    public function provideDataForStripPolishCharsMethod()
    {
        return [
            ['żółć', 'zolc'],
            ['Łódź', 'Lodz'],
            ['zażółć gęślą jaźń', 'zazolc gesla jazn'],
            [
                'Magyarország állam Közép-Európában, a Kárpát-medence közepén, amely 1989 óta parlamentáris köztársaság',
                'Magyarorszag allam Kozep-Europaban, a Karpat-medence kozepen, amely 1989 ota parlamentaris koztarsasag',
            ],
        ];
    }

    /**
     * @dataProvider provideDataForDbNumericFormat
     */
    public function testDbNumericFormat(string $locale, string $input, string $expectedOutput)
    {
        setlocale(LC_ALL, $locale);

        $actualOutput = Output::toDbNumeric($input);

        $this->assertEquals($expectedOutput, $actualOutput);
    }

    public function provideDataForDbNumericFormat()
    {
        return [
            ['pl_PL', '12 333,45', '12333.45'],
            ['pl_PL', '12 333,45 PLN', '12333.45'],
            ['pl_PL', '10000.00', '10000.00'],
            ['pl_PL', '10000', '10000'],

            ['de_DE', '12.333,45', '12333.45'],
            ['de_DE', '12.3 3 3,45', '12333.45'],
            ['de_DE', '100001', '100001'],

            ['en_GB', '581.88', '581.88'],
            ['en_GB', '87,581.88', '87581.88'],
            ['en_GB', '1000', '1000'],
            ['en_GB', '100000.00', '100000.00'],
        ];
    }

    /**
     * @param string $language
     * @param string $input
     * @param string $expectedResult
     * @return void
     *
     * @dataProvider provideDataForToPascalCaseMethod
     */
    public function testToPascalCase(string $language, string $input, string $expectedResult)
    {
        Lang::switchLanguage($language);

        $actualResult = Output::toPascalCase($input);

        $this->assertEquals($expectedResult, $actualResult);
    }

    /**
     * @return array
     */
    public function provideDataForToPascalCaseMethod(): array
    {
        return [
            ['pl', 'raz_dwa_trzy', 'RazDwaTrzy'],
            ['pl', 'kebab-case', 'KebabCase'],
            ['en', 'one_two_three', 'OneTwoThree'],
            ['en', 'kebab-case', 'KebabCase'],
            ['az', 'index-page', 'IndexPage'],
        ];
    }

    /**
     * @param string $language
     * @param string $input
     * @param string $expectedResult
     * @return void
     *
     * @dataProvider provideDataForToSnakeCaseMethod
     */
    public function testToSnakeCase(string $language, string $input, string $expectedResult)
    {
        Lang::switchLanguage($language);

        $actualResult = Output::toSnakeCase($input);

        $this->assertEquals($expectedResult, $actualResult);
    }

    /**
     * @return array
     */
    public function provideDataForToSnakeCaseMethod(): array
    {
        return [
            ['pl', 'PascalCase', 'pascal_case'],
            ['pl', 'IndexController', 'index_controller'],
            ['en', 'PascalCase', 'pascal_case'],
            ['en', 'IndexController', 'index_controller'],
            ['az', 'IndexController', 'index_controller'],
        ];
    }

    /**
     * @dataProvider provideDataForToCamelCaseMethod
     */
    public function testToCamelCase(string $language, string $input, string $expectedResult)
    {
        Lang::switchLanguage($language);

        $actualResult = Output::toCamelCase($input);

        $this->assertEquals($expectedResult, $actualResult);
    }

    /**
     * @return array
     */
    public function provideDataForToCamelCaseMethod(): array
    {
        return [
            ['pl', 'raz_dwa_trzy', 'razDwaTrzy'],
            ['pl', 'kebab-case', 'kebabCase'],
            ['en', 'one_two_three', 'oneTwoThree'],
            ['en', 'kebab-case', 'kebabCase'],
            ['az', 'index-page', 'indexPage'],
        ];
    }

    /**
     * @param string $date
     * @param $expectedOutput
     * @throws \Exception
     * @dataProvider provideDataForGetDateFromXlsMethod
     */
    public function testGetDateFromXls($date, $expectedOutput)
    {
        $actualOutput = Output::getDateFromXls($date);

        $this->assertEquals($expectedOutput, $actualOutput);
    }

    public function provideDataForGetDateFromXlsMethod()
    {
        return [
            ['2024-05-03', '2024-05-03'],
            ['2024-05-03 12:30:45', '2024-05-03 12:30:45'],
            ['1714694400', '2024-05-03'],
            [1714694400, '2024-05-03'],
            ['', false],
            ['random_string', false],
            [123, '1970-01-01'],
            ['123', '1970-01-01'],

            // @todo: currently not supported
            // ['2024-05-03 12:30', '2024-05-03'],
        ];
    }

    /**
     * @dataProvider provideDataForNumberSufixEnMethod
     */
    public function testNumberSufixEn(int $number, string $exppectedSuffix): void
    {
        Lang::switchLanguage('en');
        $suffix = Output::getNumberSuffix($number);

        $this->assertEquals($exppectedSuffix, $suffix);
    }

    public function provideDataForNumberSufixEnMethod(): array
    {
        return [
            [1, 'st'],
            [2, 'nd'],
            [3, 'rd'],
            [4, 'th'],
            [11, 'th'],
            [12, 'th'],
            [13, 'th'],
            [21, 'st'],
            [22, 'nd'],
            [23, 'rd'],
            [24, 'th'],
            [111, 'th'],
            [112, 'th'],
            [113, 'th'],
            [121, 'st'],
            [122, 'nd'],
            [123, 'rd'],
            [124, 'th'],
        ];
    }
}
