# -*- coding: utf-8 -*-
# Copyright(C) 2016 Matthieu Weber
#
# This file is part of woob.
#
# woob is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# woob is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with woob. If not, see <http://www.gnu.org/licenses/>.

import datetime
from decimal import Decimal
import pytest

from dateutil.tz import gettz
from lxml.html import fromstring

from woob.capabilities.base import NotAvailable
from woob.browser.filters.base import FilterError
from woob.browser.filters.html import FormValue, Link
from woob.browser.filters.standard import (
    RawText, DateTime, CleanText, Currency, CleanDecimal, Date,
    NumberFormatError,
)
from woob.tools.test import TestCase


class TestRawText(TestCase):
    # Original RawText behaviour:
    # - the content of <p> is empty, we return the default value
    def test_first_node_is_element(self):
        e = fromstring('<html><body><p></p></body></html>')
        self.assertEqual("foo", RawText('//p', default="foo")(e))

    # - the content of <p> starts with text, we retrieve only that text
    def test_first_node_is_text(self):
        e = fromstring('<html><body><p>blah: <span>229,90</span> EUR</p></body></html>')
        self.assertEqual("blah: ", RawText('//p', default="foo")(e))

    # - the content of <p> starts with a sub-element, we retrieve the default value
    def test_first_node_has_no_recursion(self):
        e = fromstring('<html><body><p><span>229,90</span> EUR</p></body></html>')
        self.assertEqual("foo", RawText('//p', default="foo")(e))

    # Recursive RawText behaviour
    # - the content of <p> starts with text, we retrieve all text, also the text from sub-elements
    def test_first_node_is_text_recursive(self):
        e = fromstring('<html><body><p>blah: <span>229,90</span> EUR</p></body></html>')
        self.assertEqual("blah: 229,90 EUR", RawText('//p', default="foo", children=True)(e))

    # - the content of <p> starts with a sub-element, we retrieve all text, also the text from sub-elements
    def test_first_node_is_element_recursive(self):
        e = fromstring('<html><body><p><span>229,90</span> EUR</p></body></html>')
        self.assertEqual("229,90 EUR", RawText('//p', default="foo", children=True)(e))


class TestCleanTextNewlines(TestCase):
    def setUp(self):
        self.e = fromstring('''
        <body>
            <div>
                foo
                <span>bar</span>
                baz
            </div>
        </body>
        ''')

    def test_value(self):
        self.assertEqual("foo bar baz", CleanText("//div")(self.e))
        self.assertEqual("foo baz", CleanText("//div", children=False)(self.e))
        self.assertEqual("foo\nbar\nbaz", CleanText("//div", newlines=False)(self.e))
        self.assertEqual("foo\n\nbaz", CleanText("//div", newlines=False, children=False)(self.e))


class TestFormValue(TestCase):
    def setUp(self):
        self.e = fromstring('''
        <form>
            <input value="bonjour" name="test_text">
            <input type="number" value="5" name="test_number1">
            <input type="number" step="0.01" value="0.05" name="test_number2">
            <input type="checkbox" checked="on" name="test_checkbox1">
            <input type="checkbox" name="test_checkbox2">
            <input type="range" value="20" name="test_range">
            <input type="color" value="#fff666" name="test_color">
            <input type="date" value="2010-11-12" name="test_date">
            <input type="time" value="12:13" name="test_time">
            <input type="datetime-local" value="2010-11-12T13:14" name="test_datetime_local">
        </form>
        ''')

    def test_value(self):
        self.assertEqual('bonjour', FormValue('//form//input[@name="test_text"]')(self.e))
        self.assertEqual(5, FormValue('//form//input[@name="test_number1"]')(self.e))
        self.assertEqual(Decimal('0.05'), FormValue('//form//input[@name="test_number2"]')(self.e))
        self.assertEqual(True, FormValue('//form//input[@name="test_checkbox1"]')(self.e))
        self.assertEqual(False, FormValue('//form//input[@name="test_checkbox2"]')(self.e))
        self.assertEqual(20, FormValue('//form//input[@name="test_range"]')(self.e))
        self.assertEqual('#fff666', FormValue('//form//input[@name="test_color"]')(self.e))
        self.assertEqual(datetime.date(2010, 11, 12), FormValue('//form//input[@name="test_date"]')(self.e))
        self.assertEqual(datetime.time(12, 13), FormValue('//form//input[@name="test_time"]')(self.e))
        self.assertEqual(datetime.datetime(2010, 11, 12, 13, 14), FormValue('//form//input[@name="test_datetime_local"]')(self.e))


class TestLink(TestCase):
    def test_link(self):
        e = fromstring('<a href="https://www.google.com/">Google</a>')

        self.assertEqual('https://www.google.com/', Link('//a')(e))



class TestDateTime(TestCase):
    def test_tz(self):
        self.assertEqual(
            DateTime().filter('2020-01-02 13:45:00'),
            datetime.datetime(2020, 1, 2, 13, 45)
        )
        self.assertEqual(
            DateTime(tzinfo='Europe/Paris').filter('2020-01-02 13:45:00'),
            datetime.datetime(2020, 1, 2, 13, 45, tzinfo=gettz('Europe/Paris'))
        )



def test_CleanText():
    # This test works poorly under a doctest, or would be hard to read
    assert CleanText().filter(' coucou  \n\théhé') == 'coucou héhé'
    assert CleanText().filter('coucou\xa0coucou') == CleanText().filter('coucou\xa0coucou') == 'coucou coucou'

    # Unicode normalization
    assert CleanText().filter('Éçã') == 'Éçã'
    assert CleanText(normalize='NFKC').filter('…') == '...'
    assert CleanText().filter('…') == '…'
    # Diacritical mark (dakuten)
    assert CleanText().filter('\u3053\u3099') == '\u3054'
    assert CleanText(normalize='NFD').filter('\u3053\u3099') == '\u3053\u3099'
    assert CleanText(normalize='NFD').filter('\u3054') == '\u3053\u3099'
    assert CleanText(normalize=False).filter('\u3053\u3099') == '\u3053\u3099'
    # None value
    assert_raises(FilterError, CleanText().filter, None)


def assert_raises(exc_class, func, *args, **kwargs):
    try:
        func(*args, **kwargs)
    except exc_class:
        pass
    else:
        assert False, 'did not raise %s' % exc_class


def test_CleanDecimal_unicode():
    assert CleanDecimal().filter('\u22123000') == Decimal('-3000')


def test_CleanDecimal_sign():
    assert CleanDecimal(sign='-').filter('42') == Decimal('-42')
    assert CleanDecimal(sign='-').filter('-42') == Decimal('-42')
    assert CleanDecimal(sign='+').filter('42') == Decimal('42')
    assert CleanDecimal(sign='+').filter('-42') == Decimal('42')


def test_CleanDecimal_strict():
    assert CleanDecimal.US().filter('123') == Decimal('123')
    assert CleanDecimal.US().filter('foo + 123 bar') == Decimal('123')
    assert CleanDecimal.US().filter('foo +123 bar') == Decimal('123')
    assert CleanDecimal.US().filter('foo 123.45 bar') == Decimal('123.45')
    assert CleanDecimal.US().filter('foo 12,345.67 bar') == Decimal('12345.67')
    assert CleanDecimal.US().filter('foo 123,456,789 bar') == Decimal('123456789')
    assert CleanDecimal.US().filter('foo - 123,456,789.1 bar') == Decimal('-123456789.1')
    assert CleanDecimal.US().filter('foo -123,456,789.1 bar') == Decimal('-123456789.1')
    assert CleanDecimal.US().filter('foo - .1 bar') == Decimal('-0.1')
    assert CleanDecimal.US().filter('foo -.1 bar') == Decimal('-0.1')
    assert_raises(NumberFormatError, CleanDecimal.US().filter, 'foo 12 345.67 bar')
    assert_raises(NumberFormatError, CleanDecimal.US().filter, 'foo 123 bar 456')
    assert_raises(NumberFormatError, CleanDecimal.US().filter, 'foo 123.456.789 bar')
    assert_raises(NumberFormatError, CleanDecimal.US().filter, 'foo 12,3456 bar')
    assert_raises(NumberFormatError, CleanDecimal.US().filter, 'foo 123-456 bar')

    assert CleanDecimal.French().filter('123') == Decimal('123')
    assert CleanDecimal.French().filter('foo + 123 bar') == Decimal('123')
    assert CleanDecimal.French().filter('foo +123 bar') == Decimal('123')
    assert CleanDecimal.French().filter('foo 123,45 bar') == Decimal('123.45')
    assert CleanDecimal.French().filter('foo 12 345,67 bar') == Decimal('12345.67')
    assert CleanDecimal.French().filter('foo - 123 456 789 bar') == Decimal('-123456789')
    assert CleanDecimal.French().filter('foo -123 456 789 bar') == Decimal('-123456789')
    assert_raises(NumberFormatError, CleanDecimal.French().filter, 'foo 123.45 bar')
    assert_raises(NumberFormatError, CleanDecimal.French().filter, 'foo 123 bar 456')
    assert_raises(NumberFormatError, CleanDecimal.French().filter, 'foo 123,456,789')
    assert_raises(NumberFormatError, CleanDecimal.French().filter, 'foo 12 3456 bar')
    assert_raises(NumberFormatError, CleanDecimal.French().filter, 'foo 123-456 bar')

    assert CleanDecimal.SI().filter('123') == Decimal('123')
    assert CleanDecimal.SI().filter('foo + 123 bar') == Decimal('123')
    assert CleanDecimal.SI().filter('foo +123 bar') == Decimal('123')
    assert CleanDecimal.SI().filter('foo 123.45 bar') == Decimal('123.45')
    assert CleanDecimal.SI().filter('foo 12 345.67 bar') == Decimal('12345.67')
    assert CleanDecimal.SI().filter('foo 123 456 789 bar') == Decimal('123456789')
    assert CleanDecimal.SI().filter('foo - 123 456 789 bar') == Decimal('-123456789')
    assert CleanDecimal.SI().filter('foo -123 456 789 bar') == Decimal('-123456789')
    assert_raises(NumberFormatError, CleanDecimal.SI().filter, 'foo 123,45 bar')
    assert_raises(NumberFormatError, CleanDecimal.SI().filter, 'foo 123 bar 456')
    assert_raises(NumberFormatError, CleanDecimal.SI().filter, 'foo 123,456,789')
    assert_raises(NumberFormatError, CleanDecimal.SI().filter, 'foo 12 3456 bar')
    assert_raises(NumberFormatError, CleanDecimal.SI().filter, 'foo 123-456 bar')


@pytest.mark.parametrize(
    'decimal_filter',
    (CleanDecimal.SI(), CleanDecimal.French()),
)
@pytest.mark.parametrize('input_value, expected_output', (
    ('4.1e05', Decimal('4.1e05')),
    ('4.1E05', Decimal('4.1E05')),
    ('4.1e5', Decimal('4.1e5')),
    ('4.1E5', Decimal('4.1E5')),
    ('4.1e+05', Decimal('4.1e+05')),
    ('4.1E-05', Decimal('4.1E-05')),
    ('-4.1e+5', Decimal('-4.1e+5')),
    ('+4.1E-5', Decimal('+4.1E-5')),
    ('foo 1e1 bar', Decimal('1e1')),
    ('foo -0e+1 bar', Decimal('-0e+1')),
    ('foo7719e+05bar', Decimal('7719e+05')),
    ('foo-1e0bar', Decimal('-1e0')),
))
def test_CleanDecimal_scientific_notation(decimal_filter, input_value, expected_output):
    assert decimal_filter.filter(input_value) == expected_output


@pytest.mark.parametrize(
    'decimal_filter',
    (CleanDecimal.SI(), CleanDecimal.French()),
)
@pytest.mark.parametrize('input_value', (
    ('4.1e--05'),
    ('4.1e++05'),
    ('4.1e+-05'),
    ('4.1e-+05'),
    ('4.1ee05'),
    ('+e-'),
    ('1+e-1'),
))
def test_error_CleanDecimal_scientific_notation(decimal_filter, input_value):
    with pytest.raises(NumberFormatError):
        decimal_filter.filter(input_value)


def test_Currency():
    assert Currency().filter('\u20AC') == 'EUR'
    assert Currency(default=NotAvailable).filter(None) == NotAvailable
    assert_raises(FilterError, Currency().filter, None)

def test_DateTime():
    today = datetime.datetime.now()
    assert_raises(FilterError, Date(strict=True).filter, '2019')
    assert_raises(FilterError, Date(strict=True).filter, '1788-7')
    assert_raises(FilterError, Date(strict=True).filter, 'June 1st')

    assert Date(strict=True).filter('1788-7-15') == datetime.date(1788, 7, 15)

    assert Date(strict=False).filter('1788-7-15') == datetime.date(1788, 7, 15)
    assert Date(strict=False).filter('1945-7') == datetime.date(1945, 7, today.day)
    assert Date(strict=False).filter('June 1st') == datetime.date(today.year, 6, 1)

    assert DateTime(strict=False).filter('1788-7') == datetime.datetime(1788, 7, today.day)
    assert DateTime(strict=False).filter('1788') == datetime.datetime(1788, today.month, today.day)
    assert DateTime(strict=False).filter('5-1') == datetime.datetime(today.year, 5, 1)

    assert Date(yearfirst=True).filter('88-7-15') == datetime.date(1988, 7, 15)
    assert Date(yearfirst=False).filter('20-7-15') == datetime.date(2015, 7, 20)
    assert Date(yearfirst=True).filter('1789-7-15') == datetime.date(1789, 7, 15)
    assert Date(yearfirst=True, strict=False).filter('7-15') == datetime.date(today.year, 7, 15)
