import psycopg2
import pytest
from lxml import etree as ET

from yandex.maps.wiki.tasks import states
from yandex.maps.wiki.tasks import grinder
from yandex.maps.wiki.tasks import TASKS_NAMESPACE

from helpers import extract_error

NAMESPACES = {'ns': TASKS_NAMESPACE}

TASK_NAME = 'validation'
TASK_NAME_HEAVY = 'validation.heavy'

TEST_UID = 1
TEST_BRANCH_ID = 0
TEST_COMMIT_ID = 0
TEST_TASK_ID = 1
TEST_TASK_ID_2 = 2
TEST_TASK_ID_3 = 3


def check_context(root):
    branch_element = root.find("./ns:task/ns:context/ns:validation-context/ns:branch", NAMESPACES)
    assert branch_element.text == str(TEST_BRANCH_ID)


@pytest.mark.usefixtures('setup_acl_groups')
@pytest.mark.usefixtures('setup_permissions')
@pytest.mark.usefixtures('clean_schema')
def test_validation_bad(client):
    response = client.post('/tasks', data={
        'type': TASK_NAME,
        'uid': TEST_UID
    })
    assert response.status_code == 200
    assert extract_error(response.data) == 'ERR_BAD_REQUEST'

    response = client.post('/tasks', data={
        'type': TASK_NAME,
        'uid': TEST_UID,
        'checks': 'some_check',
        'preset': 1
    })
    assert response.status_code == 200
    assert extract_error(response.data) == 'ERR_BAD_REQUEST'

    response = client.post('/tasks', data={
        'type': TASK_NAME,
        'uid': TEST_UID,
        'checks': 'some_check',
        'task-id': TEST_TASK_ID
    })
    assert response.status_code == 200
    assert extract_error(response.data) == 'ERR_BAD_REQUEST'

    response = client.post('/tasks', data={
        'type': TASK_NAME,
        'uid': TEST_UID,
        'checks': 'some_check',
        'aoi': 1,
        'region': 2
    })
    assert response.status_code == 200
    assert extract_error(response.data) == 'ERR_BAD_REQUEST'

    response = client.post('/tasks', data={
        'type': TASK_NAME,
        'uid': TEST_UID,
        'checks': 'some_check',
        'aoi': 1,
        'task-id': TEST_TASK_ID
    })
    assert response.status_code == 200
    assert extract_error(response.data) == 'ERR_BAD_REQUEST'

    response = client.post('/tasks', data={
        'type': TASK_NAME,
        'uid': TEST_UID,
        'preset': 1,
        'aoi-buffer': 1
    })
    assert response.status_code == 200
    assert extract_error(response.data) == 'ERR_BAD_REQUEST'

    response = client.post('/tasks', data={
        'type': TASK_NAME,
        'uid': TEST_UID,
        'checks': 'some_check',
        'branch': 'stable'
    })
    assert response.status_code == 200
    assert extract_error(response.data) == 'ERR_BAD_REQUEST'

    geometry = """{"type": "Point", "coordinates": [0.0, 0.0]}"""
    response = client.post('/tasks', data={
        'type': TASK_NAME,
        'uid': TEST_UID,
        'checks': 'some_check',
        'geometry': geometry
    })
    assert response.status_code == 200
    assert extract_error(response.data) == 'ERR_TOPO_INVALID_GEOMETRY'

    response = client.post('/tasks', data={
        'type': TASK_NAME,
        'uid': TEST_UID,
        'only-changed-objects': 'true'
    })
    assert response.status_code == 200
    assert extract_error(response.data) == 'ERR_BAD_REQUEST'


@pytest.mark.usefixtures('setup_acl_groups')
@pytest.mark.usefixtures('setup_permissions')
@pytest.mark.usefixtures('clean_schema')
@pytest.mark.parametrize('module_name', [TASK_NAME])
def test_validation(postgres, client, xmlschema, grinder_class):
    grinder_instance = grinder_class.return_value
    grinder_instance.submit.return_value = grinder.GrinderTaskId('test_id')
    grinder_instance.task_result.return_value = grinder.Result(states.PENDING, 'message')

    response = client.post('/tasks', data={
        'type': TASK_NAME,
        'uid': TEST_UID,
        'checks': 'some_check',
    })
    assert response.status_code == 200

    # test grinder

    grinder_instance.submit.assert_called_once_with({
        'type': TASK_NAME_HEAVY,
        'uid': TEST_UID,
        'taskId': TEST_TASK_ID,
        'branchId': TEST_BRANCH_ID,
        'commitId': TEST_COMMIT_ID,
        'checks': ['some_check']
    })

    # test brief context

    root = ET.fromstring(response.data)
    xmlschema.assertValid(root)

    check_context(root)

    # test full context and result

    response = client.get('/tasks/' + str(TEST_TASK_ID), data={
        'uid': TEST_UID
    })
    assert response.status_code == 200

    root = ET.fromstring(response.data)
    xmlschema.assertValid(root)

    check_context(root)

    # test creation from another task

    grinder_instance.submit.reset_mock()

    response = client.post('/tasks', data={
        'type': TASK_NAME,
        'uid': TEST_UID,
        'task-id': TEST_TASK_ID,
    })
    assert response.status_code == 200
    assert extract_error(response.data) == 'ERR_NO_FATAL_ERRORS'

    # insert some messages

    conn = psycopg2.connect(postgres.connection_string)
    cursor = conn.cursor()
    cursor.execute(
        "SELECT validation.insert_message_attributes(%(severity)s, %(check)s, %(description)s, %(important_region)s)",
        {'severity': 3,
         'check': 'some_check',
         'description': 'some_description',
         'important_region': True})
    attribute_id = cursor.fetchone()[0]
    cursor.execute("SELECT validation.insert_message_content(ARRAY['1:1'], ST_GeomFromEWKT('SRID=3395;POINT(0 0)'))")
    content_id = cursor.fetchone()[0]
    cursor.execute(
        "INSERT INTO validation.task_message (task_id, attributes_id, content_id) VALUES ({}, {}, {})".format(
            TEST_TASK_ID, attribute_id, content_id))
    conn.commit()

    # test full context and result

    response = client.get('/tasks/' + str(TEST_TASK_ID), data={
        'uid': TEST_UID
    })
    assert response.status_code == 200

    root = ET.fromstring(response.data)
    xmlschema.assertValid(root)

    check_context(root)

    # test creation from another task

    grinder_instance.submit.reset_mock()

    response = client.post('/tasks', data={
        'type': TASK_NAME,
        'uid': TEST_UID,
        'task-id': TEST_TASK_ID,
    })
    assert response.status_code == 200

    grinder_instance.submit.assert_called_once_with({
        'type': TASK_NAME_HEAVY,
        'uid': TEST_UID,
        'taskId': TEST_TASK_ID_2,
        'branchId': TEST_BRANCH_ID,
        'commitId': TEST_COMMIT_ID,
        'checks': ['some_check']
    })

    # test creation with parent

    grinder_instance.submit.reset_mock()

    response = client.post('/tasks', data={
        'type': TASK_NAME,
        'uid': TEST_UID,
        'checks': 'some_check',
        'parent': TEST_TASK_ID,
    })
    assert response.status_code == 200

    grinder_instance.submit.assert_called_once_with({
        'type': TASK_NAME_HEAVY,
        'uid': TEST_UID,
        'taskId': TEST_TASK_ID_3,
        'branchId': TEST_BRANCH_ID,
        'commitId': TEST_COMMIT_ID,
        'checks': ['some_check'],
        'parentTaskId': TEST_TASK_ID
    })


@pytest.mark.usefixtures('setup_acl_groups')
@pytest.mark.usefixtures('setup_permissions')
@pytest.mark.usefixtures('clean_schema')
@pytest.mark.parametrize('module_name', [TASK_NAME])
def test_validation_geometry(postgres, client, xmlschema, grinder_class):
    grinder_instance = grinder_class.return_value
    grinder_instance.submit.return_value = grinder.GrinderTaskId('test_id')
    grinder_instance.task_result.return_value = grinder.Result(states.PENDING, 'message')

    geometry = """{"type": "Polygon", "coordinates": [[
            [100.0, 0.0],
            [101.0, 0.0],
            [101.0, 1.0],
            [100.0, 1.0],
            [100.0, 0.0]
        ]]}"""

    response = client.post('/tasks', data={
        'type': TASK_NAME,
        'uid': TEST_UID,
        'checks': 'some_check',
        'geometry': geometry
    })
    assert response.status_code == 200

    # test grinder

    expected_geom = {
        'type': 'Polygon',
        'coordinates': (((11131949.07932736, -7.081154551613622e-10),
                         (11243268.57012063, -7.081154551613622e-10),
                         (11243268.57012063, 110579.9652218959),
                         (11131949.07932736, 110579.9652218959),
                         (11131949.07932736, -7.081154551613622e-10)),)
    }

    grinder_instance.submit.assert_called_once_with({
        'type': TASK_NAME,
        'uid': TEST_UID,
        'taskId': TEST_TASK_ID,
        'branchId': TEST_BRANCH_ID,
        'commitId': TEST_COMMIT_ID,
        'checks': ['some_check'],
        'aoiGeom': expected_geom
    })

    # test brief context

    root = ET.fromstring(response.data)
    xmlschema.assertValid(root)

    check_context(root)

    # test full context and result

    response = client.get('/tasks/' + str(TEST_TASK_ID), data={
        'uid': TEST_UID
    })
    assert response.status_code == 200

    root = ET.fromstring(response.data)
    xmlschema.assertValid(root)

    check_context(root)
