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.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.objects.create(
            queue=self.queue, title="Test 1", submitter_email="staff@example.com"
        )
        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.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)