# -*- coding: utf-8 -*-
import json
import os
import time
import unittest.mock

from .. import base
from girder.api import access
from girder.api.describe import describeRoute
from girder.api.rest import getApiUrl, loadmodel, Resource
from girder.constants import AccessType, registerAccessFlag, ROOT_DIR, VERSION
from girder.exceptions import AccessException, ValidationException
from girder.models.collection import Collection
from girder.models.file import File
from girder.models.folder import Folder
from girder.models.group import Group
from girder.models.item import Item
from girder.models.setting import Setting
from girder.models.user import User
from girder.settings import SettingDefault, SettingKey
from girder.utility import config


class TestEndpoints(Resource):
    def __init__(self):
        super().__init__()
        self.resourceName = 'test_endpoints'

        self.route('GET', ('loadmodel_with_flags', ':id'), self.loadModelFlags)

    @access.public
    @describeRoute(None)
    @loadmodel(model='user', level=AccessType.READ, requiredFlags='my_key')
    def loadModelFlags(self, user, params):
        return 'success'


def setUpModule():
    testServer = base.startServer()
    testServer.root.api.v1.test_endpoints = TestEndpoints()


def tearDownModule():
    base.stopServer()


class SystemTestCase(base.TestCase):
    """
    Contains tests of the /system API endpoints.
    """

    def setUp(self):
        super().setUp()

        self.users = [User().createUser(
            'usr%s' % num, 'passwd', 'tst', 'usr', 'u%s@girder.test' % num)
            for num in [0, 1]]

        self.group = Group().createGroup('test group', creator=self.users[1])

    def tearDown(self):
        # Restore the state of the plugins configuration
        conf = config.getConfig()
        if 'plugins' in conf:
            del conf['plugins']

    def testGetVersion(self):
        resp = self.request(path='/system/version', method='GET')
        self.assertEqual(resp.json['release'], VERSION['release'])

    def testSettings(self):
        users = self.users

        # Only admins should be able to get or set settings
        for method in ('GET', 'PUT', 'DELETE'):
            resp = self.request(path='/system/setting', method=method, params={
                'key': 'foo',
                'value': 'bar'
            }, user=users[1])
            self.assertStatus(resp, 403)

        # Only valid setting keys should be allowed
        resp = self.request(path='/system/setting', method='PUT', params={
            'key': 'foo',
            'value': 'bar'
        }, user=users[0])
        self.assertStatus(resp, 400)
        self.assertEqual(resp.json['field'], 'key')

        # Only a valid JSON list is permitted
        resp = self.request(path='/system/setting', method='GET', params={
            'list': json.dumps('not_a_list')
        }, user=users[0])
        self.assertStatus(resp, 400)

        resp = self.request(path='/system/setting', method='PUT', params={
            'list': json.dumps('not_a_list')
        }, user=users[0])
        self.assertStatus(resp, 400)

        # Set an invalid setting value, should fail
        resp = self.request(path='/system/setting', method='PUT', params={
            'key': SettingKey.BANNER_COLOR,
            'value': 'bar'
        }, user=users[0])
        self.assertStatus(resp, 400)
        self.assertEqual(resp.json['message'],
                         'The banner color must be a hex color triplet')

        # Set a valid value
        resp = self.request(path='/system/setting', method='PUT', params={
            'key': SettingKey.BANNER_COLOR,
            'value': '#121212'
        }, user=users[0])
        self.assertStatusOk(resp)

        # We should now be able to retrieve it
        resp = self.request(path='/system/setting', method='GET', params={
            'key': SettingKey.BANNER_COLOR
        }, user=users[0])
        self.assertStatusOk(resp)
        self.assertEqual(resp.json, '#121212')

        # We should now clear the setting
        resp = self.request(path='/system/setting', method='DELETE', params={
            'key': SettingKey.BANNER_COLOR
        }, user=users[0])
        self.assertStatusOk(resp)

        # Setting should now be default
        setting = Setting().get(SettingKey.BANNER_COLOR)
        self.assertEqual(setting, SettingDefault.defaults[SettingKey.BANNER_COLOR])

        # We should also be able to put several setting using a JSON list
        resp = self.request(path='/system/setting', method='PUT', params={
            'list': json.dumps([
                {'key': SettingKey.BANNER_COLOR, 'value': '#121212'},
                {'key': SettingKey.COOKIE_LIFETIME, 'value': None},
            ])
        }, user=users[0])
        self.assertStatusOk(resp)

        # We can get a list as well
        resp = self.request(path='/system/setting', method='GET', params={
            'list': json.dumps([
                SettingKey.BANNER_COLOR,
                SettingKey.COOKIE_LIFETIME,
            ])
        }, user=users[0])
        self.assertStatusOk(resp)
        self.assertEqual(resp.json[SettingKey.BANNER_COLOR], '#121212')

        # Try to set each key in turn to test the validation.  First test with
        # am invalid value, then test with the default value.  If the value
        # 'bad' won't trigger a validation error, the key should be present in
        # the badValues table.
        badValues = {
            SettingKey.BRAND_NAME: '',
            SettingKey.BANNER_COLOR: '',
            SettingKey.EMAIL_FROM_ADDRESS: '',
            SettingKey.PRIVACY_NOTICE: '',
            SettingKey.CONTACT_EMAIL_ADDRESS: '',
            SettingKey.EMAIL_HOST: {},
            SettingKey.SMTP_HOST: '',
            SettingKey.SMTP_PASSWORD: {},
            SettingKey.SMTP_USERNAME: {},
            SettingKey.CORS_ALLOW_ORIGIN: {},
            SettingKey.CORS_ALLOW_METHODS: {},
            SettingKey.CORS_ALLOW_HEADERS: {},
            SettingKey.CORS_EXPOSE_HEADERS: {},
        }
        allKeys = dict.fromkeys(SettingDefault.defaults.keys())
        allKeys.update(badValues)
        for key in allKeys:
            resp = self.request(path='/system/setting', method='PUT', params={
                'key': key,
                'value': badValues.get(key, 'bad')
            }, user=users[0])
            self.assertStatus(resp, 400)
            resp = self.request(path='/system/setting', method='PUT', params={
                'key': key,
                'value': json.dumps(SettingDefault.defaults.get(key, ''))
            }, user=users[0])
            self.assertStatusOk(resp)
            resp = self.request(path='/system/setting', method='PUT', params={
                'list': json.dumps([{'key': key, 'value': None}])
            }, user=users[0])
            self.assertStatusOk(resp)

    def testCheck(self):
        resp = self.request(path='/token/session', method='GET')
        self.assertStatusOk(resp)
        token = resp.json['token']
        # 'basic' mode should work for a token or for anonymous
        resp = self.request(path='/system/check', token=token)
        self.assertStatusOk(resp)
        check = resp.json
        self.assertLess(check['bootTime'], time.time())
        resp = self.request(path='/system/check')
        self.assertStatusOk(resp)
        check = resp.json
        self.assertLess(check['bootTime'], time.time())
        # but should fail for 'quick' mode
        resp = self.request(path='/system/check', token=token, params={
            'mode': 'quick'})
        self.assertStatus(resp, 401)
        # Admin can ask for any mode
        resp = self.request(path='/system/check', user=self.users[0])
        self.assertStatusOk(resp)
        check = resp.json
        self.assertLess(check['bootTime'], time.time())
        self.assertNotIn('cherrypyThreadsInUse', check)
        resp = self.request(path='/system/check', user=self.users[0], params={
            'mode': 'quick'})
        self.assertStatusOk(resp)
        check = resp.json
        self.assertLess(check['bootTime'], time.time())
        self.assertGreaterEqual(check['cherrypyThreadsInUse'], 1)
        self.assertIn('rss', check['processMemory'])
        resp = self.request(path='/system/check', user=self.users[0], params={
            'mode': 'slow'})
        self.assertStatusOk(resp)
        check = resp.json
        self.assertGreater(check['girderDiskUsage']['free'], 0)
        resp = self.request(path='/system/check', method='PUT',
                            user=self.users[0], params={'progress': True})
        self.assertStatusOk(resp)
        # tests that check repair of different models are convered in the
        # individual models' tests

    def testConsistencyCheck(self):
        user = self.users[0]
        c1 = Collection().createCollection('c1', user)
        f1 = Folder().createFolder(c1, 'f1', parentType='collection')
        Folder().createFolder(c1, 'f2', parentType='collection')
        f3 = Folder().createFolder(user, 'f3', parentType='user')
        Folder().createFolder(user, 'f4', parentType='user')
        i1 = Item().createItem('i1', user, f1)
        i2 = Item().createItem('i2', user, f1)
        Item().createItem('i3', user, f1)
        i4 = Item().createItem('i4', user, f3)
        Item().createItem('i5', user, f3)
        Item().createItem('i6', user, f3)
        assetstore = {'_id': 0}
        File().createFile(user, i1, 'foo', 7, assetstore)
        File().createFile(user, i1, 'foo', 13, assetstore)
        File().createFile(user, i2, 'foo', 19, assetstore)
        File().createFile(user, i4, 'foo', 23, assetstore)

        self.assertEqual(39, Collection().load(c1['_id'], force=True)['size'])
        self.assertEqual(39, Folder().load(f1['_id'], force=True)['size'])