import base64
import os
from datetime import datetime
from unittest import mock

import pytest
from django.contrib.contenttypes.models import ContentType
from django.utils.http import urlencode
from requests import RequestException

from passerelle.apps.atal.models import ATALConnector
from passerelle.base.models import AccessRight, ApiUser
from passerelle.utils.soap import SOAPError


def get_file(filename):
    with open(os.path.join(os.path.dirname(__file__), 'data', 'atal', filename), 'rb') as f:
        return f.read()


@pytest.fixture()
def connector(db):
    api = ApiUser.objects.create(username='all', keytype='', key='')
    connector = ATALConnector.objects.create(base_soap_url='http://example.atal.com/', slug='slug-atal')
    obj_type = ContentType.objects.get_for_model(connector)
    AccessRight.objects.create(
        codename='can_access', apiuser=api, resource_type=obj_type, resource_pk=connector.pk
    )
    return connector


def mock_atal_soap_call(monkeypatch, return_value=None, side_effect=None):
    kwargs = {}
    if return_value is not None:
        kwargs['return_value'] = return_value
    if side_effect is not None:
        kwargs['side_effect'] = side_effect
    mock_soap_call = mock.Mock(**kwargs)
    monkeypatch.setattr(ATALConnector, '_soap_call', mock_soap_call)
    return mock_soap_call


class SoapElem:
    def __init__(self, **kwargs):
        for attr, value in kwargs.items():
            setattr(self, attr, value)


REFS = [SoapElem(code='code1', libelle='elem1'), SoapElem(code='code2', libelle='elem2')]


def test_get_thematique(app, connector, monkeypatch):
    return_value = '''<?xml version="1.0" encoding="UTF-8" standalone="no"?><thematiques><thematique id="9" label="Entretien - Maintenance"></thematique>
<thematique id="12" label="Logistique"></thematique><thematique id="11" label="Réparation- Dépannage"></thematique>
<thematique id="13" label="Sinistre"></thematique><thematique id="15" label="Sécurité - Contrôle"></thematique>
<thematique id="10" label="Travaux Neufs"></thematique><thematique id="14" label="Vandalisme"></thematique></thematiques>
'''
    mock_atal_soap_call(monkeypatch, return_value=return_value)
    response = app.get('/atal/slug-atal/get-thematique')
    assert response.json['err'] == 0
    assert len(response.json['data']) == 7
    assert {'id': '9', 'text': 'Entretien - Maintenance'} in response.json['data']
    assert {'id': '14', 'text': 'Vandalisme'} in response.json['data']


def test_get_type_activite(app, connector, monkeypatch):
    mock_soap_call = mock_atal_soap_call(monkeypatch, return_value=REFS)
    response = app.get('/atal/slug-atal/get-type-activite')
    assert response.json == {
        'err': 0,
        'data': [{'text': 'elem1', 'id': 'code1'}, {'text': 'elem2', 'id': 'code2'}],
    }
    call_params = mock_soap_call.call_args.kwargs
    assert call_params['wsdl'] == 'VilleAgileService'
    assert call_params['method'] == 'getTypeActivite'


def test_get_type_de_voie(app, connector, monkeypatch):
    mock_soap_call = mock_atal_soap_call(monkeypatch, return_value=REFS)
    response = app.get('/atal/slug-atal/get-type-de-voie')
    assert response.json == {
        'err': 0,
        'data': [{'text': 'elem1', 'id': 'code1'}, {'text': 'elem2', 'id': 'code2'}],
    }
    call_params = mock_soap_call.call_args.kwargs
    assert call_params['wsdl'] == 'VilleAgileService'
    assert call_params['method'] == 'getTypeDeVoie'


def test_get_types_equipement(app, connector, monkeypatch):
    return_value = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <types>
        <type id="2" label="Espaces Verts"></type>
        <type id="4" label="Voirie">
    </type>
</types>
"""
    mock_soap_call = mock_atal_soap_call(monkeypatch, return_value=return_value)
    response = app.get('/atal/slug-atal/get-types-equipement')
    assert response.json == {
        'err': 0,
        'data': [{'text': 'Espaces Verts', 'id': '2'}, {'text': 'Voirie', 'id': '4'}],
    }
    call_params = mock_soap_call.call_args.kwargs
    assert call_params['wsdl'] == 'VilleAgileService'
    assert call_params['method'] == 'getTypesEquipement'


def test_insert_action_comment(app, connector, monkeypatch):
    mock_soap_call = mock_atal_soap_call(monkeypatch, return_value='DIT19050001')
    params = {'numero_demande': 'DIT19050001', 'commentaire': 'aaa'}
    response = app.post_json('/atal/slug-atal/insert-action-comment', params=params)
    assert response.json == {'err': 0, 'data': {'demande_number': 'DIT19050001'}}
    call_params = mock_soap_call.call_args.kwargs
    assert call_params['wsdl'] == 'DemandeService'
    assert call_params['method'] == 'insertActionComment'
    assert call_params['numeroDemande'] == 'DIT19050001'
    assert call_params['commentaire'] == 'aaa'


def test_insert_demande_complet_by_type(app, connector, monkeypatch):
    mock_soap_call = mock_atal_soap_call(monkeypatch, return_value='DIT19050001')
    params = {'type_demande': 'VOIRIE', 'coord_x': 48.866667, 'coord_y': 2.333333}
    response = app.post_json('/atal/slug-atal/insert-demande-complet-by-type', params=params)
    assert response.json == {'err': 0, 'data': {'demande_number': 'DIT19050001'}}
    call_params = mock_soap_call.call_args.kwargs
    assert call_params['wsdl'] == 'DemandeService'
    assert call_params['method'] == 'insertDemandeCompletByType'
    assert call_params['typeDemande'] == 'VOIRIE'
    assert call_params['coordX'] == 48.866667
    assert call_params['coordY'] == 2.333333


def test_upload(app, connector, monkeypatch):
    mock_soap_call = mock_atal_soap_call(monkeypatch, return_value=None)
    base64_str = 'eyJsYXN0X2NoZWNrIjoiMjAxOS0wNC0xMFQxMjowODoyOVoiL' + 'CJweXBpX3ZlcnNpb24iOiIxOS4wLjMifQ=='
    params = {'numero_demande': 'DIT19050001', 'nom_fichier': 'data.json', 'file': {'content': base64_str}}
    response = app.post_json('/atal/slug-atal/upload', params=params)
    assert response.json == {'err': 0}
    call_params = mock_soap_call.call_args.kwargs
    assert call_params['wsdl'] == 'ChargementPiecesJointesService'
    assert call_params['method'] == 'upload'
    assert call_params['numeroDemande'] == 'DIT19050001'
    assert call_params['nomFichier'] == 'data.json'
    assert call_params['donneesFichier'] == base64.b64decode(base64_str)

    # use filename sub param
    params = {'numero_demande': 'DIT19050001', 'file': {'content': base64_str, 'filename': 'nicefile.json'}}
    response = app.post_json('/atal/slug-atal/upload', params=params)
    assert response.json == {'err': 0}
    call_params = mock_soap_call.call_args.kwargs
    assert call_params['wsdl'] == 'ChargementPiecesJointesService'
    assert call_params['method'] == 'upload'
    assert call_params['numeroDemande'] == 'DIT19050001'
    assert call_params['nomFichier'] == 'nicefile.json'
    assert call_params['donneesFichier'] == base64.b64decode(base64_str)

    params = {
        'numero_demande': 'DIT19050001',
        'nom_fichier': 'data.json',
        'file': {'content': 'invalidbase64'},
    }
    response = app.post_json('/atal/slug-atal/upload', params=params)
    assert response.json == {
        'data': None,
        'err': 1,
        'err_class': 'passerelle.utils.jsonresponse.APIError',
        'err_desc': 'Invalid base64 string',
    }

    # empty file
    params = {'numero_demande': 'DIT19050001', 'nom_fichier': 'data.json', 'file': {}}
    response = app.post_json('/atal/slug-atal/upload', params=params, status=400)
    assert response.json == {
        'data': None,
        'err': 1,
        'err_class': 'passerelle.utils.jsonresponse.APIError',
        'err_desc': "file: 'content' is a required property",
    }

    # no file
    params = {'numero_demande': 'DIT19050001', 'nom_fichier': 'data.json'}
    response = app.post_json('/atal/slug-atal/upload', params=params, status=400)
    assert response.json == {
        'data': None,
        'err': 1,
        'err_class': 'passerelle.utils.jsonresponse.APIError',
        'err_desc': "'file' is a required property",
    }


def test_retrieve_details_demande(app, connector, monkeypatch):
    import passerelle.utils

    wsdl_response = mock.Mock(
        content=get_file('DemandeService.wsdl'), status_code=200, headers={'Content-Type': 'text/xml'}
    )
    monkeypatch.setattr(passerelle.utils.Request, 'get', mock.Mock(return_value=wsdl_response))

    api_response = mock.Mock(
        content=get_file('details_demande_response.xml') % b'EN ATTENTE',
        status_code=200,
        headers={'Content-Type': 'text/xml'},
    )
    monkeypatch.setattr(passerelle.utils.Request, 'post', mock.Mock(return_value=api_response))

    response = app.get('/atal/slug-atal/retrieve-details-demande/DIT18050001/')
    assert response.json['err'] == 0
    assert response.json['data']['etatDemande']['description'] == 'EN ATTENTE'


def test_retrieve_etat_travaux(app, connector, monkeypatch):
    import passerelle.utils

    wsdl_response = mock.Mock(
        content=get_file('DemandeService.wsdl'), status_code=200, headers={'Content-Type': 'text/xml'}
    )
    monkeypatch.setattr(passerelle.utils.Request, 'get', mock.Mock(return_value=wsdl_response))

    api_response = mock.Mock(
        content=get_file('etat_travaux_response.xml') % b'travaux pas commences',
        status_code=200,
        headers={'Content-Type': 'text/xml'},
    )
    monkeypatch.setattr(passerelle.utils.Request, 'post', mock.Mock(return_value=api_response))

    response = app.get('/atal/slug-atal/retrieve-etat-travaux/DIT18050001/')
    assert response.json['err'] == 0
    assert response.json['data']['libelle'] == 'travaux pas commences'


def test_infos(app, connector, monkeypatch):
    import passerelle.utils

    wsdl_response = mock.Mock(
        content=get_file('DemandeService.wsdl'), status_code=200, headers={'Content-Type': 'text/xml'}
    )
    monkeypatch.setattr(passerelle.utils.Request, 'get', mock.Mock(return_value=wsdl_response))

    api_response = mock.Mock(
        content=get_file('details_demande_response.xml') % b'EN ATTENTE',
        status_code=200,
        headers={'Content-Type': 'text/xml'},
    )
    monkeypatch.setattr(passerelle.utils.Request, 'post', mock.Mock(return_value=api_response))
    response = app.get('/atal/slug-atal/infos/DIT18050001/')

    assert response.json['err'] == 0
    assert response.json['data']['status'] == 'EN ATTENTE'

    api_response1 = mock.Mock(
        content=get_file('details_demande_response.xml') % b'PRISE EN COMPTE',
        status_code=200,
        headers={'Content-Type': 'text/xml'},
    )
    api_response2 = mock.Mock(
        content=get_file('etat_travaux_response.xml') % b'travaux pas commences',
        status_code=200,
        headers={'Content-Type': 'text/xml'},
    )
    monkeypatch.setattr(
        passerelle.utils.Request, 'post', mock.Mock(side_effect=[api_response1, api_response2])
    )

    response = app.get('/atal/slug-atal/infos/DIT18050001/')
    assert response.json['err'] == 0
    assert response.json['data']['status'] == 'travaux pas commences'

    # User comments in response
    api_response1 = mock.Mock(
        content=get_file('details_demande_response_with_comments.xml'),
        status_code=200,
        headers={'Content-Type': 'text/xml'},
    )
    api_response2 = mock.Mock(
        content=get_file('etat_travaux_response.xml') % b'travaux pas commences',
        status_code=200,
        headers={'Content-Type': 'text/xml'},
    )
    monkeypatch.setattr(
        passerelle.utils.Request, 'post', mock.Mock(side_effect=[api_response1, api_response2])
    )

    response = app.get('/atal/slug-atal/infos/DIT18050001/')
    assert response.json['err'] == 0
    data = response.json['data']
    assert data['status'] == 'travaux pas commences'
    assert data['works_comment'] == {'text': 'bonjour atal', 'date': 'Thursday 24 October 2019, 16:51'}
    assert data['works_comments'] == []
    assert data['demand_comment'] is None
    assert data['works_status'] is None

    # full in query string
    monkeypatch.setattr(
        passerelle.utils.Request, 'post', mock.Mock(side_effect=[api_response1, api_response2])
    )
    response = app.get('/atal/slug-atal/infos/DIT18050001/?full=true')
    data = response.json['data']
    assert len(data['works_comments']) == 2
    assert data['works_comments'][0] == {'text': 'OK', 'date': 'Thursday 24 October 2019, 16:48'}
    last_comment = {'text': 'bonjour atal', 'date': 'Thursday 24 October 2019, 16:51'}
    assert data['works_comments'][1] == last_comment
    assert data['works_comment'] == last_comment


def test_new_comments(app, connector, monkeypatch):
    import passerelle.utils

    wsdl_response = mock.Mock(
        content=get_file('DemandeService.wsdl'), status_code=200, headers={'Content-Type': 'text/xml'}
    )
    monkeypatch.setattr(passerelle.utils.Request, 'get', mock.Mock(return_value=wsdl_response))

    api_response = mock.Mock(
        content=get_file('details_demande_response_with_comments.xml'),
        status_code=200,
        headers={'Content-Type': 'text/xml'},
    )
    monkeypatch.setattr(passerelle.utils.Request, 'post', mock.Mock(return_value=api_response))

    all_comments = [
        {'text': 'OK', 'date': 'Thursday 24 October 2019, 16:48', 'date_raw': '2019-10-24T16:48:34+02:00'},
        {
            'text': 'bonjour atal',
            'date': 'Thursday 24 October 2019, 16:51',
            'date_raw': '2019-10-24T16:51:37+02:00',
        },
    ]

    last_datetime = datetime(year=2019, month=10, day=23)
    params = urlencode({'last_datetime': last_datetime})
    response = app.get('/atal/slug-atal/new-comments/DIT18050001/?%s' % params)
    assert response.json['err'] == 0
    assert response.json['data']['new_comments'] == all_comments
    assert response.json['data']['all_comments'] == all_comments
    assert response.json['data']['last_date'] == '2019-10-24T16:51:37+02:00'

    last_datetime = datetime(year=2019, month=10, day=24, hour=16, minute=49)
    params = urlencode({'last_datetime': last_datetime})
    response = app.get('/atal/slug-atal/new-comments/DIT18050001/?%s' % params)
    assert response.json['err'] == 0
    assert response.json['data']['new_comments'] == all_comments[1:]
    assert response.json['data']['all_comments'] == all_comments
    assert response.json['data']['last_date'] == '2019-10-24T16:51:37+02:00'

    last_datetime = datetime(year=2019, month=10, day=24, hour=16, minute=52)
    params = urlencode({'last_datetime': last_datetime})
    response = app.get('/atal/slug-atal/new-comments/DIT18050001/?%s' % params)
    assert response.json['err'] == 0
    assert response.json['data']['new_comments'] == []
    assert response.json['data']['all_comments'] == all_comments
    assert response.json['data']['last_date'] == '2019-10-24T16:51:37+02:00'


def test_check_status(app, connector, monkeypatch):
    import passerelle.utils

    wsdl_response = mock.Mock(
        content=get_file('DemandeService.wsdl'), status_code=200, headers={'Content-Type': 'text/xml'}
    )

    monkeypatch.setattr(passerelle.utils.Request, 'get', mock.Mock(return_value=wsdl_response))
    connector.check_status()

    # unset zeep cache on wsdl file
    monkeypatch.setattr(passerelle.utils.soap.InMemoryCache, 'get', mock.Mock(return_value=None))

    monkeypatch.setattr(passerelle.utils.Request, 'get', mock.Mock(side_effect=RequestException))
    with pytest.raises(SOAPError):
        connector.check_status()
