# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=invalid-name, import-outside-toplevel

import json
from io import BytesIO
from pathlib import Path
from typing import Any
from zipfile import is_zipfile, ZipFile

from pytest_mock import MockFixture

from superset import security_manager


def test_export_assets(mocker: MockFixture, client: Any) -> None:
    """
    Test exporting assets.
    """
    from superset.commands.importers.v1.utils import get_contents_from_bundle

    # grant access
    mocker.patch(
        "flask_appbuilder.security.decorators.verify_jwt_in_request", return_value=True
    )
    mocker.patch.object(security_manager, "has_access", return_value=True)

    mocked_contents = [
        (
            "metadata.yaml",
            "version: 1.0.0\ntype: assets\ntimestamp: '2022-01-01T00:00:00+00:00'\n",
        ),
        ("databases/example.yaml", "<DATABASE CONTENTS>"),
    ]

    ExportAssetsCommand = mocker.patch("superset.importexport.api.ExportAssetsCommand")
    ExportAssetsCommand().run.return_value = mocked_contents[:]

    response = client.get("/api/v1/assets/export/")
    assert response.status_code == 200

    buf = BytesIO(response.data)
    assert is_zipfile(buf)

    buf.seek(0)
    with ZipFile(buf) as bundle:
        contents = get_contents_from_bundle(bundle)
    assert contents == dict(mocked_contents)


def test_import_assets(mocker: MockFixture, client: Any) -> None:
    """
    Test importing assets.
    """
    # grant access
    mocker.patch(
        "flask_appbuilder.security.decorators.verify_jwt_in_request", return_value=True
    )
    mocker.patch.object(security_manager, "has_access", return_value=True)

    mocked_contents = {
        "metadata.yaml": (
            "version: 1.0.0\ntype: assets\ntimestamp: '2022-01-01T00:00:00+00:00'\n"
        ),
        "databases/example.yaml": "<DATABASE CONTENTS>",
    }

    ImportAssetsCommand = mocker.patch("superset.importexport.api.ImportAssetsCommand")

    root = Path("assets_export")
    buf = BytesIO()
    with ZipFile(buf, "w") as bundle:
        for path, contents in mocked_contents.items():
            with bundle.open(str(root / path), "w") as fp:
                fp.write(contents.encode())
    buf.seek(0)

    form_data = {
        "bundle": (buf, "assets_export.zip"),
        "passwords": json.dumps(
            {"assets_export/databases/imported_database.yaml": "SECRET"}
        ),
    }
    response = client.post(
        "/api/v1/assets/import/", data=form_data, content_type="multipart/form-data"
    )
    assert response.status_code == 200
    assert response.json == {"message": "OK"}

    passwords = {"assets_export/databases/imported_database.yaml": "SECRET"}
    ImportAssetsCommand.assert_called_with(mocked_contents, passwords=passwords)


def test_import_assets_not_zip(mocker: MockFixture, client: Any) -> None:
    """
    Test error message when the upload is not a ZIP file.
    """
    # grant access
    mocker.patch(
        "flask_appbuilder.security.decorators.verify_jwt_in_request", return_value=True
    )
    mocker.patch.object(security_manager, "has_access", return_value=True)

    buf = BytesIO(b"definitely_not_a_zip_file")
    form_data = {
        "bundle": (buf, "broken.txt"),
    }
    response = client.post(
        "/api/v1/assets/import/", data=form_data, content_type="multipart/form-data"
    )
    assert response.status_code == 422
    assert response.json == {
        "errors": [
            {
                "message": "Not a ZIP file",
                "error_type": "GENERIC_COMMAND_ERROR",
                "level": "warning",
                "extra": {
                    "issue_codes": [
                        {
                            "code": 1010,
                            "message": (
                                "Issue 1010 - Superset encountered an error while "
                                "running a command."
                            ),
                        }
                    ]
                },
            }
        ]
    }


def test_import_assets_no_form_data(mocker: MockFixture, client: Any) -> None:
    """
    Test error message when the upload has no form data.
    """
    # grant access
    mocker.patch(
        "flask_appbuilder.security.decorators.verify_jwt_in_request", return_value=True
    )
    mocker.patch.object(security_manager, "has_access", return_value=True)

    response = client.post("/api/v1/assets/import/", data="some_content")
    assert response.status_code == 400
    assert response.json == {
        "errors": [
            {
                "message": "Request MIME type is not 'multipart/form-data'",
                "error_type": "INVALID_PAYLOAD_FORMAT_ERROR",
                "level": "error",
                "extra": {
                    "issue_codes": [
                        {
                            "code": 1019,
                            "message": (
                                "Issue 1019 - The submitted payload has the incorrect "
                                "format."
                            ),
                        }
                    ]
                },
            }
        ]
    }


def test_import_assets_incorrect_form_data(mocker: MockFixture, client: Any) -> None:
    """
    Test error message when the upload form data has the wrong key.
    """
    # grant access
    mocker.patch(
        "flask_appbuilder.security.decorators.verify_jwt_in_request", return_value=True
    )
    mocker.patch.object(security_manager, "has_access", return_value=True)

    buf = BytesIO(b"definitely_not_a_zip_file")
    form_data = {
        "wrong": (buf, "broken.txt"),
    }
    response = client.post(
        "/api/v1/assets/import/", data=form_data, content_type="multipart/form-data"
    )
    assert response.status_code == 400
    assert response.json == {"message": "Arguments are not correct"}


def test_import_assets_no_contents(mocker: MockFixture, client: Any) -> None:
    """
    Test error message when the ZIP bundle has no contents.
    """
    # grant access
    mocker.patch(
        "flask_appbuilder.security.decorators.verify_jwt_in_request", return_value=True
    )
    mocker.patch.object(security_manager, "has_access", return_value=True)

    mocked_contents = {
        "README.txt": "Something is wrong",
    }

    root = Path("assets_export")
    buf = BytesIO()
    with ZipFile(buf, "w") as bundle:
        for path, contents in mocked_contents.items():
            with bundle.open(str(root / path), "w") as fp:
                fp.write(contents.encode())
    buf.seek(0)

    form_data = {
        "bundle": (buf, "assets_export.zip"),
        "passwords": json.dumps(
            {"assets_export/databases/imported_database.yaml": "SECRET"}
        ),
    }
    response = client.post(
        "/api/v1/assets/import/", data=form_data, content_type="multipart/form-data"
    )
    assert response.status_code == 400
    assert response.json == {
        "errors": [
            {
                "message": "No valid import files were found",
                "error_type": "GENERIC_COMMAND_ERROR",
                "level": "warning",
                "extra": {
                    "issue_codes": [
                        {
                            "code": 1010,
                            "message": (
                                "Issue 1010 - Superset encountered an error while "
                                "running a command."
                            ),
                        }
                    ]
                },
            }
        ]
    }
