from hashlib import sha1
from uuid import UUID
import json
import gzip
from io import BytesIO
import os
from glob import glob
from django.contrib.auth import get_user_model

from compat.dsn import get_header_value
from bugsink.test_utils import TransactionTestCase25251 as TransactionTestCase
from projects.models import Project, ProjectMembership
from events.models import Event
from bsmain.models import AuthToken
from bugsink.moreiterutils import batched

from .models import File, FileMetadata


User = get_user_model()


class FilesTests(TransactionTestCase):
    # Integration-test of file-upload and does-it-render-sourcemaps

    def setUp(self):
        super().setUp()
        self.user = User.objects.create_user(username='test', password='test')
        self.project = Project.objects.create()
        ProjectMembership.objects.create(project=self.project, user=self.user)
        self.client.force_login(self.user)
        self.auth_token = AuthToken.objects.create()
        self.token_headers = {"Authorization": f"Bearer {self.auth_token.token}"}

    def test_auth_no_header(self):
        response = self.client.get("/api/0/organizations/anyorg/chunk-upload/", headers={})
        self.assertEqual(401, response.status_code)
        self.assertEqual({"error": "Authorization header not found"}, response.json())

    def test_auth_empty_header(self):
        response = self.client.get("/api/0/organizations/anyorg/chunk-upload/", headers={"Authorization": ""})
        self.assertEqual(401, response.status_code)
        self.assertEqual({"error": "Authorization header not found"}, response.json())

    def test_auth_overfull_header(self):
        response = self.client.get("/api/0/organizations/anyorg/chunk-upload/", headers={"Authorization": "Bearer a b"})
        self.assertEqual(401, response.status_code)
        self.assertEqual({"error": "Expecting 'Authorization: Token abc123...' but got 'Bearer a b'"}, response.json())

    def test_auth_wrong_token(self):
        response = self.client.get("/api/0/organizations/anyorg/chunk-upload/", headers={"Authorization": "Bearer xxx"})
        self.assertEqual(401, response.status_code)
        self.assertEqual({"error": "Invalid token"}, response.json())

    def test_uuid_behavior_of_django(self):
        # test to check Django is doing the thing of converting various UUID-like things on "both sides" before
        # comparing. "this probably shouldn't be necessary" to test, but I'd rather have a test that proves it works
        # than to have to reason about it. Context: https://github.com/bugsink/bugsink/issues/105

        uuids = [
            "12345678123456781234567812345678",  # uuid_str_no_dashes
            "12345678-1234-5678-1234-567812345678",  # uuid_str_with_dashes
            UUID("12345678-1234-5678-1234-567812345678"),  # uuid_object
        ]

        file = File.objects.create(size=0)
        for create_with in uuids:
            FileMetadata.objects.all().delete()  # clean up before each test
            FileMetadata.objects.create(
                debug_id=create_with,
                file_type="source_map",
                file=file,
            )

            for test_with in uuids:
                fms = FileMetadata.objects.filter(debug_id__in=[test_with])
                self.assertEqual(1, fms.count())

    def test_assemble_artifact_bundle(self):
        SAMPLES_DIR = os.getenv("SAMPLES_DIR", "../event-samples")
        event_samples = [SAMPLES_DIR + fn for fn in [
            "/bugsink/uglifyjs-minified-sourcemaps-in-bundle.json",
            "/bugsink/uglifyjs-minified-sourcemaps-in-bundle-multi-file.json",
            ]]

        artifact_bundles = glob(SAMPLES_DIR + "/*/artifact_bundles/*.zip")

        if len(artifact_bundles) != 2:
            raise Exception(f"Not all artifact bundles found in {SAMPLES_DIR}; I insist on having some to test with.")

        for filename in artifact_bundles:
            with open(filename, 'rb') as f:
                data = f.read()

            checksum = os.path.basename(filename).split(".")[0]

            gzipped_file = BytesIO(gzip.compress(data))
            gzipped_file.name = checksum

            # 1. chunk-upload
            response = self.client.post(
                "/api/0/organizations/anyorg/chunk-upload/",
                data={"file_gzip": gzipped_file},
                headers=self.token_headers,
            )

            self.assertEqual(
                200, response.status_code, "Error in %s: %s" % (
                    filename, response.content if response.status_code != 302 else response.url))

            # 2. artifactbundle/assemble
            data = {
                "checksum": checksum,
                "chunks": [
                    checksum,  # single-chunk upload, so this works
                ],
                "projects": [
                    "unused_for_now"
                ]
            }

            response = self.client.post(
                "/api/0/organizations/anyorg/artifactbundle/assemble/",
                json.dumps(data),
                content_type="application/json",
                headers=self.token_headers,
            )

            self.assertEqual(
                200, response.status_code, "Error in %s: %s" % (
                    filename, response.content if response.status_code != 302 else response.url))

        sentry_auth_header = get_header_value(f"http://{ self.project.sentry_key }@hostisignored/{ self.project.id }")
        for filename in event_samples:
            # minimal assertions on correctness in this loop; this will be caught by our general sample-testing
            with open(filename) as f:
                data = json.loads(f.read())

            response = self.client.post(
                f"/api/{ self.project.id }/store/",
                json.dumps(data),
                content_type="application/json",
                headers={
                    "X-Sentry-Auth": sentry_auth_header,
                },
            )
            self.assertEqual(
                200, response.status_code, "Error in %s: %s" % (
                    filename, response.content if response.status_code != 302 else response.url))

        for event_id, key_phrase in [
                ("af4d4093e2d548bea61683abecb8ee95", '<span class="font-bold">captureException.js</span> in <span class="font-bold">foo</span> line <span class="font-bold">15</span>'),  # noqa
                ("ed483af389554d9cac475049ed9f560f", '<span class="font-bold">captureException.js</span> in <span class="font-bold">foo</span> line <span class="font-bold">10</span>'),  # noqa
                    ]:

            event = Event.objects.get(event_id=event_id)

            url = f'/issues/issue/{ event.issue.id }/event/{ event.id }/'
            try:
                response = self.client.get(url)

                self.assertEqual(
                    200, response.status_code, response.content if response.status_code != 302 else response.url)

                self.assertTrue(key_phrase in response.content.decode('utf-8'))

            except Exception as e:
                # we want to know _which_ event failed, hence the raise-from-e here
                raise AssertionError("Error rendering event %s" % event.event_id) from e

    def test_assemble_artifact_bundle_small_chunks(self):
        # Copy-paste of test_assemble_artifact_bundle, but checking _only_ that bundle assembly works with small chunks.
        SAMPLES_DIR = os.getenv("SAMPLES_DIR", "../event-samples")

        filename = SAMPLES_DIR + "/bugsink/artifact_bundles/51a5a327666cf1d11e23adfd55c3becad27ae769.zip"
        with open(filename, 'rb') as f:
            all_data = f.read()

        seen_checksums = []
        for data in batched(all_data, len(all_data) // 10):
            data = bytes(data)
            checksum = sha1(data).hexdigest()

            gzipped_file = BytesIO(gzip.compress(data))
            gzipped_file.name = checksum

            # 1. chunk-upload
            response = self.client.post(
                "/api/0/organizations/anyorg/chunk-upload/",
                data={"file_gzip": gzipped_file},
                headers=self.token_headers,
            )

            self.assertEqual(
                200, response.status_code, "Error in %s: %s" % (
                    filename, response.content if response.status_code != 302 else response.url))

            seen_checksums.append(checksum)

        checksum = os.path.basename(filename).split(".")[0]

        # 2. artifactbundle/assemble
        data = {
            "checksum": checksum,
            "chunks": seen_checksums,
            "projects": [
                "unused_for_now"
            ]
        }

        response = self.client.post(
            "/api/0/organizations/anyorg/artifactbundle/assemble/",
            json.dumps(data),
            content_type="application/json",
            headers=self.token_headers,
        )

        self.assertEqual(
            200, response.status_code, "Error in %s: %s" % (
                filename, response.content if response.status_code != 302 else response.url))
