# -*- coding: utf-8 -*-

import json
import pytest
import time

from girder import events
from girder.exceptions import ValidationException
from girder.models.item import Item
from girder.models.setting import Setting

from girder_jobs.constants import JobStatus
from girder_jobs.models.job import Job

# from girder_worker.girder_plugin.constants import PluginSettings as WorkerSettings
from girder_worker.girder_plugin.status import CustomJobStatus

from girder_large_image import constants
from girder_large_image.models.image_item import ImageItem

from . import girder_utilities as utilities
from .girder_utilities import unavailableWorker  # noqa


def _waitForJobToBeRunning(job):
    job = Job().load(id=job['_id'], force=True)
    while job['status'] != JobStatus.RUNNING:
        time.sleep(0.01)
        job = Job().load(id=job['_id'], force=True)
    return job


def _createThumbnails(server, admin, spec, cancel=False):
    params = {'spec': json.dumps(spec)}
    if cancel:
        params['logInterval'] = 0
    resp = server.request(
        method='PUT', path='/large_image/thumbnails', user=admin, params=params)
    assert utilities.respStatus(resp) == 200
    job = resp.json
    if cancel:
        job = _waitForJobToBeRunning(job)
        job = Job().cancelJob(job)

    starttime = time.time()
    while True:
        assert time.time() - starttime < 30
        resp = server.request('/job/%s' % str(job['_id']), user=admin)
        assert utilities.respStatus(resp) == 200
        if resp.json.get('status') == JobStatus.SUCCESS:
            return True
        if resp.json.get('status') == JobStatus.ERROR:
            return False
        if resp.json.get('status') == JobStatus.CANCELED:
            return 'canceled'
        time.sleep(0.1)


@pytest.mark.plugin('large_image')
def testSettings(server):
    for key in (constants.PluginSettings.LARGE_IMAGE_SHOW_THUMBNAILS,
                constants.PluginSettings.LARGE_IMAGE_SHOW_VIEWER,
                constants.PluginSettings.LARGE_IMAGE_AUTO_SET):
        Setting().set(key, 'false')
        assert not Setting().get(key)
        Setting().set(key, 'true')
        assert Setting().get(key)
        with pytest.raises(ValidationException) as exc:
            Setting().set(key, 'not valid')
        assert 'must be a boolean' in str(exc)
    testExtraVal = json.dumps({'images': ['label']})
    for key in (constants.PluginSettings.LARGE_IMAGE_SHOW_EXTRA,
                constants.PluginSettings.LARGE_IMAGE_SHOW_EXTRA_ADMIN):
        Setting().set(key, '')
        assert Setting().get(key) == ''
        Setting().set(key, testExtraVal)
        assert Setting().get(key) == testExtraVal
        with pytest.raises(ValidationException) as exc:
            Setting().set(key, 'not valid')
        assert 'must be a JSON' in str(exc)
        with pytest.raises(ValidationException) as exc:
            Setting().set(key, '[1]')
        assert 'must be a JSON' in str(exc)
    Setting().set(constants.PluginSettings.LARGE_IMAGE_DEFAULT_VIEWER, 'geojs')
    assert Setting().get(constants.PluginSettings.LARGE_IMAGE_DEFAULT_VIEWER) == 'geojs'
    with pytest.raises(ValidationException) as exc:
        Setting().set(constants.PluginSettings.LARGE_IMAGE_MAX_THUMBNAIL_FILES, -1)
    assert 'must be a non-negative integer' in str(exc)
    Setting().set(constants.PluginSettings.LARGE_IMAGE_MAX_THUMBNAIL_FILES, 5)
    assert Setting().get(constants.PluginSettings.LARGE_IMAGE_MAX_THUMBNAIL_FILES) == 5
    with pytest.raises(ValidationException) as exc:
        Setting().set(constants.PluginSettings.LARGE_IMAGE_MAX_SMALL_IMAGE_SIZE, -1)
    assert 'must be a non-negative integer' in str(exc)
    Setting().set(constants.PluginSettings.LARGE_IMAGE_MAX_SMALL_IMAGE_SIZE, 1024)
    assert Setting().get(constants.PluginSettings.LARGE_IMAGE_MAX_SMALL_IMAGE_SIZE) == 1024
    # Test the large_image/settings end point
    resp = server.request('/large_image/settings', user=None)
    settings = resp.json
    # The values were set earlier
    assert settings[constants.PluginSettings.LARGE_IMAGE_DEFAULT_VIEWER] == 'geojs'
    assert settings[constants.PluginSettings.LARGE_IMAGE_SHOW_VIEWER] is True
    assert settings[constants.PluginSettings.LARGE_IMAGE_SHOW_THUMBNAILS] is True
    assert settings[constants.PluginSettings.LARGE_IMAGE_SHOW_EXTRA] == testExtraVal
    assert settings[constants.PluginSettings.LARGE_IMAGE_SHOW_EXTRA_ADMIN] == testExtraVal
    assert settings[constants.PluginSettings.LARGE_IMAGE_AUTO_SET] is True
    assert settings[constants.PluginSettings.LARGE_IMAGE_MAX_THUMBNAIL_FILES] == 5
    assert settings[constants.PluginSettings.LARGE_IMAGE_MAX_SMALL_IMAGE_SIZE] == 1024


@pytest.mark.plugin('large_image')
def testThumbnailFileJob(server, admin, user, fsAssetstore):
    file = utilities.uploadExternalFile('data/sample_image.ptif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])

    # We should report zero thumbnails
    item = Item().load(itemId, user=admin)
    present, removed = ImageItem().removeThumbnailFiles(item, keep=10)
    assert present == 0

    # Test PUT thumbnails
    resp = server.request(method='PUT', path='/large_image/thumbnails', user=user)
    assert utilities.respStatus(resp) == 403
    resp = server.request(method='PUT', path='/large_image/thumbnails', user=admin)
    assert utilities.respStatus(resp) == 400
    assert '"spec" is required' in resp.json['message']
    resp = server.request(
        method='PUT', path='/large_image/thumbnails', user=admin,
        params={'spec': json.dumps({})})
    assert utilities.respStatus(resp) == 400
    assert 'must be a JSON list' in resp.json['message']

    # Run a job to create two sizes of thumbnails
    assert _createThumbnails(server, admin, [
        {'width': 160, 'height': 100},
        {'encoding': 'PNG'}
    ])

    # We should report two thumbnails
    present, removed = ImageItem().removeThumbnailFiles(item, keep=10)
    assert present == 2

    # Run a job to create two sizes of thumbnails, one different than
    # before
    assert _createThumbnails(server, admin, [
        {'width': 160, 'height': 100},
        {'width': 160, 'height': 160},
    ])
    # We should report three thumbnails
    present, removed = ImageItem().removeThumbnailFiles(item, keep=10)
    assert present == 3

    # Asking for a bad thumbnail specification should just do nothing
    assert not _createThumbnails(server, admin, ['not a dictionary'])
    present, removed = ImageItem().removeThumbnailFiles(item, keep=10)
    assert present == 3

    # Test GET thumbnails
    resp = server.request(path='/large_image/thumbnails', user=user)
    assert utilities.respStatus(resp) == 403
    resp = server.request(
        path='/large_image/thumbnails', user=admin,
        params={'spec': json.dumps({})})
    assert utilities.respStatus(resp) == 400
    assert 'must be a JSON list' in resp.json['message']
    resp = server.request(path='/large_image/thumbnails', user=admin)
    assert utilities.respStatus(resp) == 200
    assert resp.json == 3
    resp = server.request(
        path='/large_image/thumbnails', user=admin,
        params={'spec': json.dumps([{'width': 160, 'height': 100}])})
    assert utilities.respStatus(resp) == 200
    assert resp.json == 1

    # Test DELETE thumbnails
    resp = server.request(method='DELETE', path='/large_image/thumbnails', user=user)
    assert utilities.respStatus(resp) == 403
    resp = server.request(
        method='DELETE', path='/large_image/thumbnails', user=admin,
        params={'spec': json.dumps({})})
    assert utilities.respStatus(resp) == 400
    assert 'must be a JSON list' in resp.json['message']

    # Delete one set of thumbnails
    resp = server.request(
        method='DELETE', path='/large_image/thumbnails', user=admin,
        params={'spec': json.dumps([{'encoding': 'PNG'}])})
    assert utilities.respStatus(resp) == 200
    present, removed = ImageItem().removeThumbnailFiles(item, keep=10)
    assert present == 2

    # Try to delete some that don't exist
    resp = server.request(
        method='DELETE', path='/large_image/thumbnails', user=admin,
        params={'spec': json.dumps([{'width': 200, 'height': 200}])})
    assert utilities.respStatus(resp) == 200
    present, removed = ImageItem().removeThumbnailFiles(item, keep=10)
    assert present == 2

    # Delete them all
    resp = server.request(
        method='DELETE', path='/large_image/thumbnails', user=admin)
    assert utilities.respStatus(resp) == 200
    present, removed = ImageItem().removeThumbnailFiles(item, keep=10)
    assert present == 0

    # We should be able to cancel a job
    slowList = [
        {'width': 1600, 'height': 1000},
        {'width': 3200, 'height': 2000},
        {'width': 1600, 'height': 1002},
        {'width': 1600, 'height': 1003},
        {'width': 1600, 'height': 1004},
    ]
    assert _createThumbnails(server, admin, slowList, cancel=True) == 'canceled'
    present, removed = ImageItem().removeThumbnailFiles(item, keep=10)
    assert present < 3 + len(slowList)


@pytest.mark.usefixtures('unavailableWorker')  # noqa
@pytest.mark.plugin('large_image')
def testDeleteIncompleteTile(server, admin, user, fsAssetstore, unavailableWorker):  # noqa
    # Test the large_image/settings end point
    resp = server.request(
        method='DELETE', path='/large_image/tiles/incomplete', user=user)
    assert utilities.respStatus(resp) == 403
    resp = server.request(
        method='DELETE', path='/large_image/tiles/incomplete', user=admin)
    assert utilities.respStatus(resp) == 200
    results = resp.json
    assert results['removed'] == 0

    file = utilities.uploadTestFile('yb10kx5k.png', admin, fsAssetstore)
    itemId = str(file['itemId'])
    resp = server.request(
        method='POST', path='/item/%s/tiles' % itemId, user=admin)
    resp = server.request(
        method='DELETE', path='/large_image/tiles/incomplete',
        user=admin)
    assert utilities.respStatus(resp) == 200
    results = resp.json
    assert results['removed'] == 1

    def preventCancel(evt):
        job = evt.info['job']
        params = evt.info['params']
        if (params.get('status') and
                params.get('status') != job['status'] and
                params['status'] in (JobStatus.CANCELED, CustomJobStatus.CANCELING)):
            evt.preventDefault()

    # Prevent a job from cancelling
    events.bind('jobs.job.update', 'testDeleteIncompleteTile', preventCancel)
    # Create a job and mark it as running
    resp = server.request(
        method='POST', path='/item/%s/tiles' % itemId, user=admin)
    job = Job().load(id=resp.json['_id'], force=True)
    Job().updateJob(job, status=JobStatus.RUNNING)

    resp = server.request(
        method='DELETE', path='/large_image/tiles/incomplete',
        user=admin)
    events.unbind('jobs.job.update', 'testDeleteIncompleteTile')
    assert utilities.respStatus(resp) == 200
    results = resp.json
    assert results['removed'] == 0
    assert 'could not be canceled' in results['message']
    # Now we should be able to cancel the job
    resp = server.request(
        method='DELETE', path='/large_image/tiles/incomplete',
        user=admin)
    assert utilities.respStatus(resp) == 200
    results = resp.json
    assert results['removed'] == 1


@pytest.mark.plugin('large_image')
def testCaches(server, admin):
    resp = server.request(path='/large_image/cache', user=admin)
    assert utilities.respStatus(resp) == 200
    results = resp.json
    assert 'tilesource' in results
    resp = server.request(path='/large_image/cache/clear', method='PUT', user=admin)
    assert utilities.respStatus(resp) == 200
    results = resp.json
    assert 'cacheCleared' in results


@pytest.mark.plugin('large_image')
def testAssociateImageCaching(server, admin, user, fsAssetstore):
    file = utilities.uploadExternalFile('data/sample_image.ptif.sha512', admin, fsAssetstore)
    itemId = str(file['itemId'])
    resp = server.request(path='/item/%s/tiles/images/label' % itemId,
                          user=admin, isJson=False)
    assert utilities.respStatus(resp) == 200
    # Test GET associated_images
    resp = server.request(path='/large_image/associated_images', user=user)
    assert utilities.respStatus(resp) == 403
    resp = server.request(path='/large_image/associated_images', user=admin)
    assert utilities.respStatus(resp) == 200
    assert resp.json == 1
    # Test DELETE associated_images
    resp = server.request(
        method='DELETE', path='/large_image/associated_images', user=user)
    assert utilities.respStatus(resp) == 403
    resp = server.request(
        method='DELETE', path='/large_image/associated_images', user=admin)
    assert utilities.respStatus(resp) == 200
    assert resp.json == 1
    resp = server.request(path='/large_image/associated_images', user=admin)
    assert utilities.respStatus(resp) == 200
    assert resp.json == 0
