import base64
from collections import OrderedDict
from datetime import datetime
from django.contrib.auth.models import User
from django.core.files.uploadedfile import SimpleUploadedFile
from freezegun import freeze_time
from helpdesk.models import CustomField, Queue, Ticket
from rest_framework import HTTP_HEADER_ENCODING
from rest_framework.exceptions import ErrorDetail
from rest_framework.status import (
    HTTP_200_OK,
    HTTP_201_CREATED,
    HTTP_204_NO_CONTENT,
    HTTP_400_BAD_REQUEST,
    HTTP_403_FORBIDDEN
)
from rest_framework.test import APITestCase


class TicketTest(APITestCase):
    due_date = datetime(2022, 4, 10, 15, 6)

    @classmethod
    def setUpTestData(cls):
        cls.queue = Queue.objects.create(
            title='Test Queue',
            slug='test-queue',
        )

    def test_create_api_ticket_not_authenticated_user(self):
        response = self.client.post('/api/tickets/')
        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)

    def test_create_api_ticket_authenticated_non_staff_user(self):
        non_staff_user = User.objects.create_user(username='test')
        self.client.force_authenticate(non_staff_user)
        response = self.client.post('/api/tickets/')
        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)

    def test_create_api_ticket_no_data(self):
        staff_user = User.objects.create_user(username='test', is_staff=True)
        self.client.force_authenticate(staff_user)
        response = self.client.post('/api/tickets/')
        self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST)
        self.assertEqual(response.data, {
            'queue': [ErrorDetail(string='This field is required.', code='required')],
            'title': [ErrorDetail(string='This field is required.', code='required')]
        })
        self.assertFalse(Ticket.objects.exists())

    def test_create_api_ticket_wrong_date_format(self):
        staff_user = User.objects.create_user(username='test', is_staff=True)
        self.client.force_authenticate(staff_user)
        response = self.client.post('/api/tickets/', {
            'queue': self.queue.id,
            'title': 'Test title',
            'due_date': 'monday, 1st of may 2022'
        })
        self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST)
        self.assertEqual(response.data, {
            'due_date': [ErrorDetail(string='Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].', code='invalid')]
        })
        self.assertFalse(Ticket.objects.exists())

    def test_create_api_ticket_authenticated_staff_user(self):
        staff_user = User.objects.create_user(username='test', is_staff=True)
        self.client.force_authenticate(staff_user)
        response = self.client.post('/api/tickets/', {
            'queue': self.queue.id,
            'title': 'Test title',
            'description': 'Test description\nMulti lines',
            'submitter_email': 'test@mail.com',
            'priority': 4
        })
        self.assertEqual(response.status_code, HTTP_201_CREATED)
        created_ticket = Ticket.objects.get()
        self.assertEqual(created_ticket.title, 'Test title')
        self.assertEqual(created_ticket.description,
                         'Test description\nMulti lines')
        self.assertEqual(created_ticket.submitter_email, 'test@mail.com')
        self.assertEqual(created_ticket.priority, 4)
        self.assertEqual(created_ticket.followup_set.count(), 1)

    def test_create_api_ticket_with_basic_auth(self):
        username = 'admin'
        password = 'admin'
        User.objects.create_user(
            username=username, password=password, is_staff=True)

        test_user = User.objects.create_user(username='test')
        merge_ticket = Ticket.objects.create(
            queue=self.queue, title='merge ticket')

        # Generate base64 credentials string
        credentials = f"{username}:{password}"
        base64_credentials = base64.b64encode(credentials.encode(
            HTTP_HEADER_ENCODING)).decode(HTTP_HEADER_ENCODING)

        self.client.credentials(
            HTTP_AUTHORIZATION=f"Basic {base64_credentials}")
        response = self.client.post(
            '/api/tickets/',
            {
                'queue': self.queue.id,
                'title': 'Title',
                'description': 'Description',
                'resolution': 'Resolution',
                'assigned_to': test_user.id,
                'submitter_email': 'test@mail.com',
                'status': Ticket.RESOLVED_STATUS,
                'priority': 1,
                'on_hold': True,
                'due_date': self.due_date,
                'merged_to': merge_ticket.id
            }
        )

        self.assertEqual(response.status_code, HTTP_201_CREATED)
        created_ticket = Ticket.objects.last()
        self.assertEqual(created_ticket.title, 'Title')
        self.assertEqual(created_ticket.description, 'Description')
        # resolution can not be set on creation
        self.assertIsNone(created_ticket.resolution)
        self.assertEqual(created_ticket.assigned_to, test_user)
        self.assertEqual(created_ticket.submitter_email, 'test@mail.com')
        self.assertEqual(created_ticket.priority, 1)
        # on_hold is False on creation
        self.assertFalse(created_ticket.on_hold)
        # status is always open on creation
        self.assertEqual(created_ticket.status, Ticket.OPEN_STATUS)
        self.assertEqual(created_ticket.due_date, self.due_date)
        # merged_to can not be set on creation
        self.assertIsNone(created_ticket.merged_to)

    def test_edit_api_ticket(self):
        staff_user = User.objects.create_user(username='admin', is_staff=True)
        test_ticket = Ticket.objects.create(
            queue=self.queue, title='Test ticket')

        test_user = User.objects.create_user(username='test')
        merge_ticket = Ticket.objects.create(
            queue=self.queue, title='merge ticket')

        self.client.force_authenticate(staff_user)
        response = self.client.put(
            '/api/tickets/%d/' % test_ticket.id,
            {
                'queue': self.queue.id,
                'title': 'Title',
                'description': 'Description',
                'resolution': 'Resolution',
                'assigned_to': test_user.id,
                'submitter_email': 'test@mail.com',
                'status': Ticket.RESOLVED_STATUS,
                'priority': 1,
                'on_hold': True,
                'due_date': self.due_date,
                'merged_to': merge_ticket.id
            }
        )

        self.assertEqual(response.status_code, HTTP_200_OK)
        test_ticket.refresh_from_db()
        self.assertEqual(test_ticket.title, 'Title')
        self.assertEqual(test_ticket.description, 'Description')
        self.assertEqual(test_ticket.resolution, 'Resolution')
        self.assertEqual(test_ticket.assigned_to, test_user)
        self.assertEqual(test_ticket.submitter_email, 'test@mail.com')
        self.assertEqual(test_ticket.priority, 1)
        self.assertTrue(test_ticket.on_hold)
        self.assertEqual(test_ticket.status, Ticket.RESOLVED_STATUS)
        self.assertEqual(test_ticket.due_date, self.due_date)
        self.assertEqual(test_ticket.merged_to, merge_ticket)

    def test_partial_edit_api_ticket(self):
        staff_user = User.objects.create_user(username='admin', is_staff=True)
        test_ticket = Ticket.objects.create(
            queue=self.queue, title='Test ticket')

        self.client.force_authenticate(staff_user)
        response = self.client.patch(
            '/api/tickets/%d/' % test_ticket.id,
            {
                'description': 'New description',
            }
        )

        self.assertEqual(response.status_code, HTTP_200_OK)
        test_ticket.refresh_from_db()
        self.assertEqual(test_ticket.description, 'New description')

    def test_delete_api_ticket(self):
        staff_user = User.objects.create_user(username='admin', is_staff=True)
        test_ticket = Ticket.objects.create(
            queue=self.queue, title='Test ticket')
        self.client.force_authenticate(staff_user)
        response = self.client.delete('/api/tickets/%d/' % test_ticket.id)
        self.assertEqual(response.status_code, HTTP_204_NO_CONTENT)
        self.assertFalse(Ticket.objects.exists())

    @freeze_time('2022-06-30 23:09:44')
    def test_create_api_ticket_with_custom_fields(self):
        # Create custom fields
        for field_type, field_display in CustomField.DATA_TYPE_CHOICES:
            extra_data = {}
            if field_type in ('varchar', 'text'):
                extra_data['max_length'] = 10
            if field_type == 'integer':
                # Set one field as required to test error if not provided
                extra_data['required'] = True
            if field_type == 'decimal':
                extra_data['max_length'] = 7
                extra_data['decimal_places'] = 3
            if field_type == 'list':
                extra_data['list_values'] = '''Green
                Blue
                Red
                Yellow'''
            CustomField.objects.create(
                name=field_type, label=field_display, data_type=field_type, **extra_data)

        staff_user = User.objects.create_user(username='test', is_staff=True)
        self.client.force_authenticate(staff_user)

        # Test creation without providing required field
        response = self.client.post('/api/tickets/', {
            'queue': self.queue.id,
            'title': 'Test title',
            'description': 'Test description\nMulti lines',
            'submitter_email': 'test@mail.com',
            'priority': 4
        })
        self.assertEqual(response.status_code, HTTP_400_BAD_REQUEST)
        self.assertEqual(response.data, {'custom_integer': [ErrorDetail(
            string='This field is required.', code='required')]})

        # Test creation with custom field values
        response = self.client.post('/api/tickets/', {
            'queue': self.queue.id,
            'title': 'Test title',
            'description': 'Test description\nMulti lines',
            'submitter_email': 'test@mail.com',
            'priority': 4,
            'custom_varchar': 'test',
            'custom_text': 'multi\nline',
            'custom_integer': '1',
            'custom_decimal': '42.987',
            'custom_list': 'Red',
            'custom_boolean': True,
            'custom_date': '2022-4-11',
            'custom_time': '23:59:59',
            'custom_datetime': '2022-4-10 18:27',
            'custom_email': 'email@test.com',
            'custom_url': 'http://django-helpdesk.readthedocs.org/',
            'custom_ipaddress': '127.0.0.1',
            'custom_slug': 'test-slug',
        })
        self.assertEqual(response.status_code, HTTP_201_CREATED)
        # Check all fields with data returned from the response
        self.assertEqual(response.data, {
            'id': 1,
            'queue': 1,
            'title': 'Test title',
            'description': 'Test description\nMulti lines',
            'resolution': None,
            'submitter_email': 'test@mail.com',
            'assigned_to': None,
            'status': 1,
            'on_hold': False,
            'priority': 4,
            'due_date': None,
            'merged_to': None,
            'followup_set': [OrderedDict([
                ('id', 1),
                ('ticket', 1),
                ('user', 1),
                ('title', 'Ticket Opened'),
                ('comment', 'Test description\nMulti lines'),
                ('public', True),
                ('new_status', None),
                ('time_spent', None),
                ('followupattachment_set', []),
                ('date', '2022-06-30T23:09:44'),
                ('message_id', None),
            ])],
            'custom_varchar': 'test',
            'custom_text': 'multi\nline',
            'custom_integer': 1,
            'custom_decimal': '42.987',
            'custom_list': 'Red',
            'custom_boolean': True,
            'custom_date': '2022-04-11',
            'custom_time': '23:59:59',
            'custom_datetime': '2022-04-10T18:27',
            'custom_email': 'email@test.com',
            'custom_url': 'http://django-helpdesk.readthedocs.org/',
            'custom_ipaddress': '127.0.0.1',
            'custom_slug': 'test-slug'
        })

    def test_create_api_ticket_with_attachment(self):
        staff_user = User.objects.create_user(username='test', is_staff=True)
        self.client.force_authenticate(staff_user)
        test_file = SimpleUploadedFile(
            'file.jpg', b'file_content', content_type='image/jpg')
        response = self.client.post('/api/tickets/', {
            'queue': self.queue.id,
            'title': 'Test title',
            'description': 'Test description\nMulti lines',
            'submitter_email': 'test@mail.com',
            'priority': 4,
            'attachment': test_file
        })
        self.assertEqual(response.status_code, HTTP_201_CREATED)
        created_ticket = Ticket.objects.get()
        self.assertEqual(created_ticket.title, 'Test title')
        self.assertEqual(created_ticket.description,
                         'Test description\nMulti lines')
        self.assertEqual(created_ticket.submitter_email, 'test@mail.com')
        self.assertEqual(created_ticket.priority, 4)
        self.assertEqual(created_ticket.followup_set.count(), 1)
        self.assertEqual(created_ticket.followup_set.get(
        ).followupattachment_set.count(), 1)
        attachment = created_ticket.followup_set.get().followupattachment_set.get()
        self.assertEqual(
            attachment.file.name,
            f'helpdesk/attachments/test-queue-1-{created_ticket.secret_key}/1/file.jpg'
        )

    def test_create_follow_up_with_attachments(self):
        staff_user = User.objects.create_user(username='test', is_staff=True)
        self.client.force_authenticate(staff_user)
        ticket = Ticket.objects.create(queue=self.queue, title='Test')
        test_file_1 = SimpleUploadedFile(
            'file.jpg', b'file_content', content_type='image/jpg')
        test_file_2 = SimpleUploadedFile(
            'doc.pdf', b'Doc content', content_type='application/pdf')

        response = self.client.post('/api/followups/', {
            'ticket': ticket.id,
            'title': 'Test',
            'comment': 'Test answer\nMulti lines',
            'attachments': [
                test_file_1,
                test_file_2
            ]
        })
        self.assertEqual(response.status_code, HTTP_201_CREATED)
        created_followup = ticket.followup_set.last()
        self.assertEqual(created_followup.title, 'Test')
        self.assertEqual(created_followup.comment, 'Test answer\nMulti lines')
        self.assertEqual(created_followup.followupattachment_set.count(), 2)
        self.assertEqual(
            created_followup.followupattachment_set.first().filename, 'doc.pdf')
        self.assertEqual(
            created_followup.followupattachment_set.first().mime_type, 'application/pdf')
        self.assertEqual(
            created_followup.followupattachment_set.last().filename, 'file.jpg')
        self.assertEqual(
            created_followup.followupattachment_set.last().mime_type, 'image/jpg')


class UserTicketTest(APITestCase):
    def setUp(self):
        self.queue = Queue.objects.create(title='Test queue')
        self.user = User.objects.create_user(username='test')
        self.client.force_authenticate(self.user)

    def test_get_user_tickets(self):
        user = User.objects.create_user(username='test2', email="foo@example.com")
        ticket_1 = Ticket.objects.create(
            queue=self.queue, title='Test 1',
            submitter_email="foo@example.com")
        ticket_2 = Ticket.objects.create(
            queue=self.queue, title='Test 2',
            submitter_email="bar@example.com")
        ticket_3 = Ticket.objects.create(
            queue=self.queue, title='Test 3',
            submitter_email="foo@example.com")
        self.client.force_authenticate(user)
        response = self.client.get('/api/user_tickets/')
        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(len(response.data["results"]), 2)
        self.assertEqual(response.data["results"][0]['id'], ticket_3.id)
        self.assertEqual(response.data["results"][1]['id'], ticket_1.id)

    def test_staff_user(self):
        staff_user = User.objects.create_user(username='test2', is_staff=True, email="staff@example.com")
        ticket_1 = Ticket.objects.create(
            queue=self.queue, title='Test 1',
            submitter_email="staff@example.com")
        ticket_2 = Ticket.objects.create(
            queue=self.queue, title='Test 2',
            submitter_email="foo@example.com")
        self.client.force_authenticate(staff_user)
        response = self.client.get('/api/user_tickets/')
        self.assertEqual(response.status_code, HTTP_200_OK)
        self.assertEqual(len(response.data["results"]), 1)

    def test_not_logged_in_user(self):
        ticket_1 = Ticket.objects.create(
            queue=self.queue, title='Test 1',
            submitter_email="ex@example.com")
        self.client.logout()
        response = self.client.get('/api/user_tickets/')
        self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)