import io
import unittest

from collections import defaultdict
from mock import MagicMock

from storage import StorageContext
from storage.swift import SwiftStorage


class MockSwiftStorage(SwiftStorage):
  def __init__(self, *args, **kwargs):
    super(MockSwiftStorage, self).__init__(*args, **kwargs)
    self._connection = MagicMock()

  def _get_connection(self):
    return self._connection


class MockSwiftTests(unittest.TestCase):
  base_args = {
    'context': StorageContext('nyc', None, None),
    'swift_container': 'container-name',
    'storage_path': '/basepath',
    'auth_url': 'https://auth.com',
    'swift_user': 'root',
    'swift_password': 'password',
  }

  def test_fixed_path_concat(self):
    swift = MockSwiftStorage(**self.base_args)
    swift.exists('object/path')
    swift._get_connection().head_object.assert_called_with('container-name', 'basepath/object/path')


  def test_simple_path_concat(self):
    simple_concat_args = dict(self.base_args)
    simple_concat_args['simple_path_concat'] = True
    swift = MockSwiftStorage(**simple_concat_args)
    swift.exists('object/path')
    swift._get_connection().head_object.assert_called_with('container-name', 'basepathobject/path')

  def test_delete_unknown_path(self):
    swift = SwiftStorage(**self.base_args)
    with self.assertRaises(IOError):
      swift.remove('someunknownpath')


class FakeSwiftStorage(SwiftStorage):
  def __init__(self, *args, **kwargs):
    super(FakeSwiftStorage, self).__init__(*args, **kwargs)
    self._connection = FakeSwift()

  def _get_connection(self):
    return self._connection


class FakeSwift(object):
  def __init__(self):
    self.containers = defaultdict(dict)

  def head_object(self, container, path):
    return self.containers[container].get(path)

  def put_object(self, container, path, content, chunk_size=None, content_type=None, headers=None):
    if not isinstance(content, str):
      content = content.read()

    self.containers[container][path] = {
      'content': content,
      'chunk_size': chunk_size,
      'content_type': content_type,
      'headers': headers,
    }

  def get_object(self, container, path, resp_chunk_size=None):
    data = self.containers[container].get(path, {})
    if 'X-Object-Manifest' in data['headers']:
      new_contents = []
      prefix = data['headers']['X-Object-Manifest']
      for key, value in self.containers[container].iteritems():
        if ('container-name/' + key).startswith(prefix):
          new_contents.append((key, value['content']))

      new_contents.sort(key=lambda value: value[0])

      data = dict(data)
      data['content'] = ''.join([nc[1] for nc in new_contents])
      return bool(data), data.get('content')

    return bool(data), data.get('content')

  def delete_object(self, container, path):
    self.containers[container].pop(path, None)


class FakeQueue(object):
  def __init__(self):
    self.items = []

  def get(self):
    if not self.items:
      return None

    return self.items.pop()

  def put(self, names, item, available_after=0):
    self.items.append({
      'names': names,
      'item': item,
      'available_after': available_after,
    })

class FakeSwiftTests(unittest.TestCase):
  base_args = {
    'context': StorageContext('nyc', None, None),
    'swift_container': 'container-name',
    'storage_path': '/basepath',
    'auth_url': 'https://auth.com',
    'swift_user': 'root',
    'swift_password': 'password',
  }

  def test_simple_put_get(self):
    swift = FakeSwiftStorage(**self.base_args)
    self.assertFalse(swift.exists('somepath'))

    swift.put_content('somepath', 'hello world!')
    self.assertTrue(swift.exists('somepath'))

    self.assertEquals('hello world!', swift.get_content('somepath'))

  def test_stream_read_write(self):
    swift = FakeSwiftStorage(**self.base_args)
    self.assertFalse(swift.exists('somepath'))

    swift.stream_write('somepath', io.BytesIO('some content here'))
    self.assertTrue(swift.exists('somepath'))

    self.assertEquals('some content here', swift.get_content('somepath'))
    self.assertEquals('some content here', ''.join(list(swift.stream_read('somepath'))))

  def test_remove(self):
    swift = FakeSwiftStorage(**self.base_args)
    self.assertFalse(swift.exists('somepath'))

    swift.put_content('somepath', 'hello world!')
    self.assertTrue(swift.exists('somepath'))

    swift.remove('somepath')
    self.assertFalse(swift.exists('somepath'))

  def test_checksum(self):
    swift = FakeSwiftStorage(**self.base_args)
    swift.put_content('somepath', 'hello world!')
    self.assertIsNotNone(swift.get_checksum('somepath'))

  def test_chunked_upload(self):
    swift = FakeSwiftStorage(**self.base_args)
    uuid, metadata = swift.initiate_chunked_upload()

    chunks = ['this', 'is', 'some', 'chunked', 'data', '']
    offset = 0
    for chunk in chunks:
      bytes_written, metadata, error = swift.stream_upload_chunk(uuid, offset, len(chunk),
                                                                 io.BytesIO(chunk), metadata)
      self.assertIsNone(error)
      self.assertEquals(bytes_written, len(chunk))
      offset += len(chunk)

    swift.complete_chunked_upload(uuid, 'somepath', metadata)
    self.assertEquals(''.join(chunks), swift.get_content('somepath'))

  def test_cancel_chunked_upload(self):
    swift = FakeSwiftStorage(**self.base_args)
    uuid, metadata = swift.initiate_chunked_upload()

    chunks = ['this', 'is', 'some', 'chunked', 'data', '']
    offset = 0
    for chunk in chunks:
      bytes_written, metadata, error = swift.stream_upload_chunk(uuid, offset, len(chunk),
                                                                 io.BytesIO(chunk), metadata)
      self.assertIsNone(error)
      self.assertEquals(bytes_written, len(chunk))
      offset += len(chunk)

    swift.cancel_chunked_upload(uuid, metadata)
    for segment in SwiftStorage._segment_list_from_metadata(metadata):
      self.assertFalse(swift.exists(segment.path))

  def test_empty_chunks_queued_for_deletion(self):
    chunk_cleanup_queue = FakeQueue()
    args = dict(self.base_args)
    args['context'] = StorageContext('nyc', None, chunk_cleanup_queue)

    swift = FakeSwiftStorage(**args)
    uuid, metadata = swift.initiate_chunked_upload()

    chunks = ['this', '', 'is', 'some', '', 'chunked', 'data', '']
    offset = 0
    for chunk in chunks:
      length = len(chunk)
      if length == 0:
        length = 1

      bytes_written, metadata, error = swift.stream_upload_chunk(uuid, offset, length,
                                                                 io.BytesIO(chunk), metadata)
      self.assertIsNone(error)
      self.assertEquals(bytes_written, len(chunk))
      offset += len(chunk)

    swift.complete_chunked_upload(uuid, 'somepath', metadata)
    self.assertEquals(''.join(chunks), swift.get_content('somepath'))

    # Check the chunk deletion queue and ensure we have the last chunk queued.
    found = chunk_cleanup_queue.get()
    self.assertIsNotNone(found)

    found2 = chunk_cleanup_queue.get()
    self.assertIsNone(found2)

if __name__ == '__main__':
  unittest.main()