import datetime
import json
from datetime import datetime, timedelta
from unittest import mock
from urllib.parse import urlencode

import pytest
from django.contrib.auth.models import User
from django.urls import reverse
from django.utils import timezone
from rest_framework.test import APIClient

from incident.models import Incident, IncidentRecommendations, IncidentEffect
from perms.models import Perm


@pytest.mark.django_db
class TestIncidentsListAPI:

    @pytest.fixture
    def now(self):
        return datetime.now(tz=timezone.utc)

    @pytest.fixture
    def incidents(self, now):
        Incident.objects.bulk_create([
            Incident(timestamp=now, title='Test1', importance=50, event_count=10, events=''),
            Incident(timestamp=now - timedelta(hours=1),
                     title='Test2',
                     importance=20,
                     event_count=10,
                     events=''),
            Incident(timestamp=now - timedelta(days=1),
                     title='Test3',
                     importance=10,
                     event_count=10,
                     events=''),
        ])

    @pytest.fixture
    def users_and_incidents(self, add_user_with_permissions):
        for number in range(1, 4):
            user = add_user_with_permissions(username=f'user_{number}', password='pwd12',
                                             permissions=[Perm.can_view_incidents_list])
            
            Incident.objects.create(
                timestamp=datetime.now() - timedelta(hours=1),
                title=f'Test_{number}',
                importance=20,
                event_count=10,
                status=1,
                assigned_to=user,
                events='')
            Incident.objects.create(
                timestamp=datetime.now() - timedelta(hours=1),
                title=f'Test_{number + 1}',
                importance=20 + 2,
                event_count=10 + 3,
                status=1,
                assigned_to=user,
                events='')
            Incident.objects.create(
                timestamp=datetime.now() - timedelta(hours=1),
                title=f'Test_{number}',
                importance=20,
                event_count=10,
                status=2,
                assigned_to=user,
                events='')
            Incident.objects.create(
                timestamp=datetime.now() - timedelta(hours=1),
                title=f'Test_{number}',
                importance=20,
                event_count=10,
                status=3,
                assigned_to=user,
                events='')

        add_user_with_permissions(username=f'user_admin', password='pwd12',
                                  permissions=[Perm.can_view_incidents_list])

    @pytest.fixture(autouse=True)
    def setup_tests(self, client, django_user_model, test_server, add_user_with_permissions):
        # User who can work with incidents
        self.user = add_user_with_permissions(username="pro100ton3", password="ponala61", permissions=[
            Perm.can_view_incidents_list,
            Perm.can_view_incidents,
            Perm.can_work_with_incidents
        ], is_superuser=False)

    @pytest.mark.unit
    def test_full_list(self, incidents, client):
        response = client.get(reverse('incident-list'))
        assert response.status_code == 403

    @pytest.mark.unit
    @pytest.mark.skip(reason='Works 1.3, 1.5 waiting of products')
    def test_sorted_list_var_1(self, incidents, client, django_user_model):
        """Test user see all unseegned incidents"""
        client.force_login(django_user_model.objects.get(username='pro100ton3'))
        response = client.get(reverse('incident-list'))
        assert response.status_code == 200
        data = json.loads(response.content.decode('utf-8'))
        assert data["count"] == 3

    @pytest.mark.unit
    @pytest.mark.skip(reason='Works 1.3, 1.5 waiting of products')
    def test_sorted_list_var_2(self, incidents, client, django_user_model):
        """Check that the user see the incidents assigned to him and does not see the resolved ones """
        user = django_user_model.objects.get(username='pro100ton3')
        incident_signed = Incident.objects.get(title='Test1')
        incident_signed.assigned_to = user
        incident_signed.save()
        for incident in Incident.objects.filter(assigned_to=None):
            incident.status = 3
            incident.save()
        client.force_login(user)
        response = client.get(reverse('incident-list'))
        assert response.status_code == 200
        data = json.loads(response.content.decode('utf-8'))
        assert data["count"] == 1
        assert data["results"][0]["assigned_to"] == "pro100ton3"
        assert data["results"][0]["status"] == 1

    @pytest.mark.unit
    @pytest.mark.skip(reason='Works 1.3, 1.5 waiting of products')
    def test_sorted_list_var_3(self, incidents, client, django_user_model, add_user_with_permissions):
        """Check that the user sees the incidents assigned to him and all the unassigned ones """
        user = django_user_model.objects.get(username='pro100ton3')
        user_2 = add_user_with_permissions(username='any_one', password='ao_pwd', is_superuser=False)
        incident_assigned = Incident.objects.get(title='Test1')
        incident_assigned.assigned_to = user
        incident_assigned.save()
        incident_assigned_to_any = Incident.objects.get(title='Test2')
        incident_assigned_to_any.assigned_to = user_2
        incident_assigned_to_any.save()
        client.force_login(user)
        response = client.get(reverse('incident-list'))
        assert response.status_code == 200
        data = json.loads(response.content.decode('utf-8'))
        assert data["count"] == 3
        count = 0
        for item in data["results"]:
            if (item["assigned_to"] == 'pro100ton3' or item["assigned_to"] == 'any_one') and item["status"] == 1:
                count += 1
        assert count == 2

    @pytest.mark.unit
    @pytest.mark.skip(reason='Works 1.3, 1.5 waiting of products')
    def test_list_with_search(self, incidents, client, add_user_with_permissions) -> None:
        _url = reverse('incident-list')
        user = add_user_with_permissions(username='test_user1',
                                         password='pwd12',
                                         permissions=[Perm.can_view_incidents_list])
        client.force_login(user=user)
        querystring = urlencode({
            'format': 'datatables',
            'draw': '14',
            'columns[0][data]': 'user_friendly_id',
            'columns[0][name]': '',
            'columns[0][searchable]': 'true',
            'columns[0][orderable]': 'true',
            'columns[0][search][value]': '',
            'columns[0][search][regex]': 'false',
            'start': '0',
            'length': '10',
            'search[value]': 'test_search',
            'search[regex]': 'false',
            '_': '1635401184016',
        })
        url = f'{_url}?{querystring}'
        response = client.get(url)
        assert response.status_code == 200

    @pytest.mark.unit
    @pytest.mark.skip(reason='Works 1.3, 1.5 waiting of products')
    def test_filter_show_resolve_incident(self, incidents, client, add_user_with_permissions) -> None:
        _url = reverse('incident-list')
        user = add_user_with_permissions(username='test_user1', password='pwd12',
                                         permissions=[Perm.can_view_incidents_list])
        stats_resolved = Incident.Status.RESOLVED.value
        incident = Incident.objects.last()
        incident.status = stats_resolved
        incident.save()

        client.force_login(user=user)
        querystring = urlencode({
            'format': 'datatables',
            'draw': '14',
            'columns[0][data]': 'status',
            'columns[0][name]': '',
            'columns[0][searchable]': 'false',
            'columns[0][orderable]': 'true',
            'columns[0][search][value]': '',
            'columns[0][search][regex]': ' false',
            'start': '0',
            'length': '10',
            'search[value]': '',
            'search[regex]': 'false',
            'filter[0][field]': 'status',
            'filter[0][value][]': str(stats_resolved),
            'filter[0][filter_type]': 'exact-match',
            '_': '1635500017602',
        })
        url = f'{_url}?{querystring}'
        response = client.get(url)
        assert response.status_code == 200
        data = json.loads(response.content.decode('utf-8'))
        assert data['recordsFiltered'] == 1
        assert data['data'][0]['status'] == stats_resolved

    @pytest.mark.unit
    @mock.patch('incident.views.incidents_api.incident_count_notification_to_ws')
    def test_update_action_valid(self, _, incidents):
        incident = Incident.objects.first()
        client = APIClient()
        client.force_login(self.user)
        url = reverse('incident-detail', args=[incident.pk])
        data = {'status': 3}
        response = client.put(url, data, format='json')
        assert response.status_code == 200
        response_data = response.data
        assert 'status' in response_data
        assert 'deadline' in response_data
        assert 'category' in response_data
        assert 'comment' in response_data
        assert 'assigned_to' in response_data
        assert response_data['status'] == 3

    @pytest.mark.unit
    def test_update_action_not_valid(self, incidents):
        data = {
            'title': 'must be not change',
            'status': 'string',
        }
        incident = Incident.objects.first()
        client = APIClient()
        client.force_login(self.user)
        url = reverse('incident-detail', args=[incident.pk])
        response = client.put(url, data, format='json')
        print(response.data)
        assert response.status_code == 400

    @pytest.mark.unit
    def test_effects_and_recommendation_in_incident(self, incidents):
        incident = Incident.objects.first()
        rec = IncidentRecommendations.objects.create(name='rec')
        eff = IncidentEffect.objects.create(name='eff')
        incident.close_recommendations.add(rec)
        incident.effects.add(eff)

        client = APIClient()
        client.force_authenticate(self.user)
        url = reverse('incident-detail', args=[incident.pk])
        response = client.get(url)
        assert response.status_code == 200
        assert response.data['close_recommendations'][0] == {'id': rec.pk, 'name': 'rec', 'description': None}
        assert response.data['effects'][0] == {'id': eff.pk, 'name': 'eff', 'description': None}

    @pytest.mark.unit
    def test_incident_ordering(self, users_and_incidents):
        """The check is that we are logged in by different users and the first 2 incidents in the list should be
        incidents assigned to this user. We also log in as a user who has no incidents assigned at all and the list
        should have incidents sorted by user"""
        self.check_list_ordering()
        # Login user with assigned incidents
        for user in ["user_1", "user_2", "user_3"]:
            self.check_list_ordering_assigned(user)

    def check_list_ordering_assigned(self, username):
        client = APIClient()
        url = reverse('incident-list')
        logout_url = reverse('logout')
        user = User.objects.get(username=username)
        client.force_login(user=user)
        response = client.get(url)
        data = json.loads(response.content.decode('utf-8'))
        assert response.status_code == 200
        assert data['results'][0]['assigned_to'] == username
        assert data['results'][1]['assigned_to'] == username
        response = client.post(logout_url)
        assert response.status_code == 200

    def check_list_ordering(self):
        client = APIClient()
        url = reverse('incident-list')
        logout_url = reverse('logout')
        user = User.objects.get(username="user_admin")
        client.force_login(user=user)
        response = client.get(url)
        data = json.loads(response.content.decode('utf-8'))
        assert response.status_code == 200
        assert data['results'][0]['assigned_to'] == "user_1"
        assert data['results'][1]['assigned_to'] == "user_1"
        response = client.post(logout_url)
        assert response.status_code == 200
