# Copyright 2018 Google Inc.
#
# Licensed 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.

"""Integration tests for the firebase_admin.project_management module."""

import json
import plistlib
import random

import pytest

from firebase_admin import exceptions
from firebase_admin import project_management


TEST_APP_BUNDLE_ID = 'com.firebase.adminsdk-python-integration-test'
TEST_APP_PACKAGE_NAME = 'com.firebase.adminsdk_python_integration_test'
TEST_APP_DISPLAY_NAME_PREFIX = 'Created By Firebase AdminSDK Python Integration Testing'

SHA_1_HASH_1 = '123456789a123456789a123456789a123456789a'
SHA_1_HASH_2 = 'aaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbb'
SHA_256_HASH_1 = '123456789a123456789a123456789a123456789a123456789a123456789a1234'
SHA_256_HASH_2 = 'cafef00dba5eba11b01dfaceacc01adeda7aba5eca55e77e0b57ac1e5ca1ab1e'
SHA_1 = project_management.SHACertificate.SHA_1
SHA_256 = project_management.SHACertificate.SHA_256


def _starts_with(display_name, prefix):
    return display_name and display_name.startswith(prefix)


@pytest.fixture(scope='module')
def android_app(default_app):
    del default_app
    android_apps = project_management.list_android_apps()
    for android_app in android_apps:
        if _starts_with(android_app.get_metadata().display_name, TEST_APP_DISPLAY_NAME_PREFIX):
            return android_app
    return project_management.create_android_app(
        package_name=TEST_APP_PACKAGE_NAME, display_name=TEST_APP_DISPLAY_NAME_PREFIX)


@pytest.fixture(scope='module')
def ios_app(default_app):
    del default_app
    ios_apps = project_management.list_ios_apps()
    for ios_app in ios_apps:
        if _starts_with(ios_app.get_metadata().display_name, TEST_APP_DISPLAY_NAME_PREFIX):
            return ios_app
    return project_management.create_ios_app(
        bundle_id=TEST_APP_BUNDLE_ID, display_name=TEST_APP_DISPLAY_NAME_PREFIX)


def test_create_android_app_already_exists(android_app):
    del android_app

    with pytest.raises(exceptions.AlreadyExistsError) as excinfo:
        project_management.create_android_app(
            package_name=TEST_APP_PACKAGE_NAME, display_name=TEST_APP_DISPLAY_NAME_PREFIX)
    assert 'Requested entity already exists' in str(excinfo.value)
    assert excinfo.value.cause is not None
    assert excinfo.value.http_response is not None


def test_android_set_display_name_and_get_metadata(android_app, project_id):
    app_id = android_app.app_id
    android_app = project_management.android_app(app_id)
    new_display_name = '{0} helloworld {1}'.format(
        TEST_APP_DISPLAY_NAME_PREFIX, random.randint(0, 10000))

    android_app.set_display_name(new_display_name)
    metadata = project_management.android_app(app_id).get_metadata()
    android_app.set_display_name(TEST_APP_DISPLAY_NAME_PREFIX)  # Revert the display name.

    assert metadata._name == 'projects/{0}/androidApps/{1}'.format(project_id, app_id)
    assert metadata.app_id == app_id
    assert metadata.project_id == project_id
    assert metadata.display_name == new_display_name
    assert metadata.package_name == TEST_APP_PACKAGE_NAME


def test_list_android_apps(android_app):
    del android_app

    android_apps = project_management.list_android_apps()

    assert any(_starts_with(android_app.get_metadata().display_name, TEST_APP_DISPLAY_NAME_PREFIX)
               for android_app in android_apps)


def test_get_android_app_config(android_app, project_id):
    config = android_app.get_config()

    json_config = json.loads(config)
    assert json_config['project_info']['project_id'] == project_id
    for client in json_config['client']:
        client_info = client['client_info']
        if client_info['mobilesdk_app_id'] == android_app.app_id:
            assert client_info['android_client_info']['package_name'] == TEST_APP_PACKAGE_NAME
            break
    else:
        pytest.fail('Failed to find the test Android app in the Android config.')


def test_android_sha_certificates(android_app):
    """Tests all of get_sha_certificates, add_sha_certificate, and delete_sha_certificate."""
    # Delete all existing certs.
    for cert in android_app.get_sha_certificates():
        android_app.delete_sha_certificate(cert)

    # Add four different certs and assert that they have all been added successfully.
    android_app.add_sha_certificate(project_management.SHACertificate(SHA_1_HASH_1))
    android_app.add_sha_certificate(project_management.SHACertificate(SHA_1_HASH_2))
    android_app.add_sha_certificate(project_management.SHACertificate(SHA_256_HASH_1))
    android_app.add_sha_certificate(project_management.SHACertificate(SHA_256_HASH_2))

    cert_list = android_app.get_sha_certificates()

    sha_1_hashes = set(cert.sha_hash for cert in cert_list if cert.cert_type == SHA_1)
    sha_256_hashes = set(cert.sha_hash for cert in cert_list if cert.cert_type == SHA_256)
    assert sha_1_hashes == set([SHA_1_HASH_1, SHA_1_HASH_2])
    assert sha_256_hashes == set([SHA_256_HASH_1, SHA_256_HASH_2])
    for cert in cert_list:
        assert cert.name

    # Adding the same cert twice should cause an already-exists error.
    with pytest.raises(exceptions.AlreadyExistsError) as excinfo:
        android_app.add_sha_certificate(project_management.SHACertificate(SHA_256_HASH_2))
    assert 'Requested entity already exists' in str(excinfo.value)
    assert excinfo.value.cause is not None
    assert excinfo.value.http_response is not None

    # Delete all certs and assert that they have all been deleted successfully.
    for cert in cert_list:
        android_app.delete_sha_certificate(cert)

    assert android_app.get_sha_certificates() == []

    # Deleting a nonexistent cert should cause a not-found error.
    with pytest.raises(exceptions.NotFoundError) as excinfo:
        android_app.delete_sha_certificate(cert_list[0])
    assert 'Requested entity was not found' in str(excinfo.value)
    assert excinfo.value.cause is not None
    assert excinfo.value.http_response is not None


def test_create_ios_app_already_exists(ios_app):
    del ios_app

    with pytest.raises(exceptions.AlreadyExistsError) as excinfo:
        project_management.create_ios_app(
            bundle_id=TEST_APP_BUNDLE_ID, display_name=TEST_APP_DISPLAY_NAME_PREFIX)
    assert 'Requested entity already exists' in str(excinfo.value)
    assert excinfo.value.cause is not None
    assert excinfo.value.http_response is not None


def test_ios_set_display_name_and_get_metadata(ios_app, project_id):
    app_id = ios_app.app_id
    ios_app = project_management.ios_app(app_id)
    new_display_name = '{0} helloworld {1}'.format(
        TEST_APP_DISPLAY_NAME_PREFIX, random.randint(0, 10000))

    ios_app.set_display_name(new_display_name)
    metadata = project_management.ios_app(app_id).get_metadata()
    ios_app.set_display_name(TEST_APP_DISPLAY_NAME_PREFIX)  # Revert the display name.

    assert metadata._name == 'projects/{0}/iosApps/{1}'.format(project_id, app_id)
    assert metadata.app_id == app_id
    assert metadata.project_id == project_id
    assert metadata.display_name == new_display_name
    assert metadata.bundle_id == TEST_APP_BUNDLE_ID


def test_list_ios_apps(ios_app):
    del ios_app

    ios_apps = project_management.list_ios_apps()

    assert any(_starts_with(ios_app.get_metadata().display_name, TEST_APP_DISPLAY_NAME_PREFIX)
               for ios_app in ios_apps)


def test_get_ios_app_config(ios_app, project_id):
    config = ios_app.get_config()

    # In Python 2.7, the plistlib module works with strings, while in Python 3, it is significantly
    # redesigned and works with bytes objects instead.
    try:
        plist = plistlib.loads(config.encode('utf-8'))
    except AttributeError:  # Python 2.7 plistlib does not have the loads attribute.
        plist = plistlib.readPlistFromString(config)  # pylint: disable=no-member
    assert plist['BUNDLE_ID'] == TEST_APP_BUNDLE_ID
    assert plist['PROJECT_ID'] == project_id
    assert plist['GOOGLE_APP_ID'] == ios_app.app_id