import base64
import os
import json
import uuid

import pytest

from contextlib import contextmanager
from collections import namedtuple
from httmock import urlmatch, HTTMock

from util.config.provider import KubernetesConfigProvider

def normalize_path(path):
  return path.replace('/', '_')

@contextmanager
def fake_kubernetes_api(tmpdir_factory, files=None):
  hostname = 'kubapi'
  service_account_token_path = str(tmpdir_factory.mktemp("k8s").join("serviceaccount"))
  auth_header = str(uuid.uuid4())

  with open(service_account_token_path, 'w') as f:
    f.write(auth_header)

  global secret
  secret = {
    'data': {}
  }

  def write_file(config_dir, filepath, value):
    normalized_path = normalize_path(filepath)
    absolute_path = str(config_dir.join(normalized_path))
    try:
      os.makedirs(os.path.dirname(absolute_path))
    except OSError:
      pass

    with open(absolute_path, 'w') as f:
      f.write(value)

  config_dir = tmpdir_factory.mktemp("config")
  if files:
    for filepath, value in files.iteritems():
      normalized_path = normalize_path(filepath)
      write_file(config_dir, filepath, value)
      secret['data'][normalized_path] = base64.b64encode(value)

  @urlmatch(netloc=hostname,
            path='/api/v1/namespaces/quay-enterprise/secrets/quay-enterprise-config-secret$',
            method='get')
  def get_secret(_, __):
    return {'status_code': 200, 'content': json.dumps(secret)}

  @urlmatch(netloc=hostname,
            path='/api/v1/namespaces/quay-enterprise/secrets/quay-enterprise-config-secret$',
            method='put')
  def put_secret(_, request):
    updated_secret = json.loads(request.body)
    for filepath, value in updated_secret['data'].iteritems():
      if filepath not in secret['data']:
        # Add
        write_file(config_dir, filepath, base64.b64decode(value))

    for filepath in secret['data']:
      if filepath not in updated_secret['data']:
        # Remove.
        normalized_path = normalize_path(filepath)
        os.remove(str(config_dir.join(normalized_path)))

    secret['data'] = updated_secret['data']
    return {'status_code': 200, 'content': json.dumps(secret)}

  @urlmatch(netloc=hostname, path='/api/v1/namespaces/quay-enterprise$')
  def get_namespace(_, __):
    return {'status_code': 200, 'content': json.dumps({})}

  @urlmatch(netloc=hostname)
  def catch_all(url, _):
    print url
    return {'status_code': 404, 'content': '{}'}

  with HTTMock(get_secret, put_secret, get_namespace, catch_all):
    provider = KubernetesConfigProvider(str(config_dir), 'config.yaml', 'config.py',
                                        api_host=hostname,
                                        service_account_token_path=service_account_token_path)

    # Validate all the files.
    for filepath, value in files.iteritems():
      normalized_path = normalize_path(filepath)
      assert provider.volume_file_exists(normalized_path)
      with provider.get_volume_file(normalized_path) as f:
        assert f.read() == value

    yield provider


def test_basic_config(tmpdir_factory):
  basic_files = {
    'config.yaml': 'FOO: bar',
  }

  with fake_kubernetes_api(tmpdir_factory, files=basic_files) as provider:
    assert provider.config_exists()
    assert provider.get_config() is not None
    assert provider.get_config()['FOO'] == 'bar'


@pytest.mark.parametrize('filepath', [
  'foo',
  'foo/meh',
  'foo/bar/baz',
])
def test_remove_file(filepath, tmpdir_factory):
  basic_files = {
    filepath: 'foo',
  }

  with fake_kubernetes_api(tmpdir_factory, files=basic_files) as provider:
    normalized_path = normalize_path(filepath)
    assert provider.volume_file_exists(normalized_path)
    provider.remove_volume_file(normalized_path)
    assert not provider.volume_file_exists(normalized_path)


class TestFlaskFile(object):
  def save(self, buf):
    buf.write('hello world!')


def test_save_file(tmpdir_factory):
  basic_files = {}

  with fake_kubernetes_api(tmpdir_factory, files=basic_files) as provider:
    assert not provider.volume_file_exists('testfile')
    flask_file = TestFlaskFile()
    provider.save_volume_file(flask_file, 'testfile')
    assert provider.volume_file_exists('testfile')