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

import json

from vaporize.core import convert_datetime, get_url, handle_request, query
from vaporize.utils import DotDict

BACKUP_WEEKLY_DISABLED  = 'DISABLED'
BACKUP_WEEKLY_SUNDAY    = 'SUNDAY'
BACKUP_WEEKLY_MONDAY    = 'MONDAY'
BACKUP_WEEKLY_TUESDAY   = 'TUESDAY'
BACKUP_WEEKLY_WEDNESDAY = 'WEDNESDAY'
BACKUP_WEEKLY_THURSDAY  = 'THURSDAY'
BACKUP_WEEKLY_FRIDAY    = 'FRIDAY'
BACKUP_WEEKLY_SATURDAY  = 'SATURDAY'

BACKUP_DAILY_DISABLED    = 'DISABLED'
BACKUP_DAILY_H_0000_0200 = 'H_0000_0200'
BACKUP_DAILY_H_0200_0400 = 'H_0200_0400'
BACKUP_DAILY_H_0400_0600 = 'H_0400_0600'
BACKUP_DAILY_H_0600_0800 = 'H_0600_0800'
BACKUP_DAILY_H_0800_1000 = 'H_0800_1000'
BACKUP_DAILY_H_1000_1200 = 'H_1000_1200'
BACKUP_DAILY_H_1200_1400 = 'H_1200_1400'
BACKUP_DAILY_H_1400_1600 = 'H_1400_1600'
BACKUP_DAILY_H_1600_1800 = 'H_1600_1800'
BACKUP_DAILY_H_1800_2000 = 'H_1800_2000'
BACKUP_DAILY_H_2000_2200 = 'H_2000_2200'
BACKUP_DAILY_H_2200_0000 = 'H_2200_0000'


class BackupSchedule(DotDict):
    """A CloudServers Backup Schedule."""
    def __repr__(self):
        if 'daily' in self:
            return '<BackupSchedule %s>' % self['daily']
        if 'weekly' in self:
            return '<BackupSchedule %s>' % self['weekly']
        return super(BackupSchedule, self).__repr__()

    @classmethod
    def create(cls, weekly=None, daily=None):
        return cls(weekly=weekly, daily=daily)

    def to_dict(self):
        ret = {'backupSchedule': {'enabled': True}}
        if self.weekly:
            ret['backupSchedule']['weekly'] = self.weekly
        if self.daily:
            ret['backupSchedule']['daily'] = self.daily
        return ret


class Flavor(DotDict):
    """A CloudServers Flavor."""
    def __repr__(self):
        if 'name' in self:
            return '<Flavor %s>' % self['name']
        return super(Flavor, self).__repr__()

    @classmethod
    def list(cls, limit=None, offset=None, detail=False):
        """Returns a list of Flavors.

        :param limit: Limit the result set by a number
        :type limit: int
        :param offset: Offset the result set by a number
        :type offset: int
        :param detail: Return additional details about each Flavor
        :type: bool
        :returns: A list of CloudServers Flavors.
        :rtype: :class:`Flavor`

        .. versionadded:: 0.1
        """
        url = [get_url('cloudservers'), 'flavors']
        if detail:
            url.append('detail')
        url = '/'.join(url)
        if limit is not None or offset is not None:
            url = query(url, limit=limit, offset=offset)
        return handle_request('get', url, wrapper=cls, container='flavors')

    @classmethod
    def find(cls, id):
        """Returns a Flavor by ID.

        :param id: The ID of the Flavor to retrieve
        :type id: int
        :returns: A CloudServers Flavor matching the ID.
        :rtype: :class:`Flavor`

        .. versionadded:: 0.1
        """
        url = '/'.join([get_url('cloudservers'), 'flavors', str(id)])
        return handle_request('get', url, wrapper=cls, container='flavor')


class Image(DotDict):
    """A CloudServers Image."""
    def __repr__(self):
        if 'name' in self:
            return '<Image %s>' % self['name']
        return super(Image, self).__repr__()

    def __setitem__(self, key, value):
        if key == 'serverId':
            key = 'server_id'
        elif key in ['created', 'updated']:
            value = convert_datetime(value)
        super(Image, self).__setitem__(key, value)

    def reload(self):
        """Reload this Image (an implicit :func:`get`).

        :returns: An updated CloudServers Image.
        :rtype: :class:`Image`

        .. versionadded:: 0.1.9
        """
        assert 'id' in self, "Missing Image ID"
        response = Image.find(self['id'])
        self.update(response)
        return self

    def delete(self):
        """Delete this Image.

        .. note::

            You can only delete Images you create.

        .. warning::

            There is not confirmation step for this operation. Deleting an
            image is permanent.

        .. versionadded:: 0.1
        """
        assert 'id' in self
        url = '/'.join([get_url('cloudservers'), 'images', str(self['id'])])
        handle_request('delete', url)

    @classmethod
    def list(cls, limit=None, offset=None, detail=False):
        """Returns a list of CloudServers Images.

        :param limit: Limit the result set by a cetain number
        :type limit: int
        :param offset: Offset the result set by a certain number
        :type offset: int
        :param detail: Return additional details about each Image
        :type detail: bool
        :returns: A list of CloudServers Images.
        :rtype: A list of :class:`Image`

        .. versionadded:: 0.1
        """
        url = [get_url('cloudservers'), 'images']
        if detail:
            url.append('detail')
        url = '/'.join(url)
        if limit is not None or offset is not None:
            url = query(url, limit=limit, offset=offset)
        return handle_request('get', url, wrapper=Image, container='images')

    @classmethod
    def find(cls, id):
        """Return an Image by ID.

        :param id: The ID of the Image to retrieve
        :type id: int
        :returns: A CloudServer Image matching the ID.
        :rtype: :class:`Image`

        .. versionadded:: 0.1
        """
        url = '/'.join([get_url('cloudservers'), 'images', str(id)])
        return handle_request('get', url, wrapper=cls, container='image')

    @classmethod
    def create(cls, name, server):
        """Create an Image.

        :param name: Name of the Image
        :type name: str
        :param server: Server or ``id`` to base the Image upon
        :type server: int or :class:`Server`
        :returns: A shiny new CloudServers Image.
        :rtype: :class:`Image`

        .. versionadded:: 0.1
        """
        if isinstance(server, Server):
            server = server.id
        server = int(server)
        data = {'image': {'serverId': server,
                          'name': name}}
        data = json.dumps(data)
        url = '/'.join([get_url('cloudservers'), 'images'])
        return handle_request('post', url, data, cls, 'image')


class IP(DotDict):
    """A CloudServers IP Address."""
    def __repr__(self):
        if 'public' in self:
            return '<IP %s>' % self['public'][0]
        if 'private' in self:
            return '<IP %s>' % self['private'][0]
        return super(IP, self).__repr__()


class Server(DotDict):
    """A CloudServers Server."""
    def __repr__(self):
        if 'name' in self:
            return "<Server %s>" % self['name']
        return super(Server, self).__repr__()

    def __setitem__(self, key, value):
        if key == 'addresses':
            value = IP(value)
        super(Server, self).__setitem__(key, value)

    def reload(self):
        """Reload this Server (an implicit :func:`get`).

        :returns: An updated CloudServers Server.
        :rtype: :class:`Server`

        .. versionadded:: 0.1
        """
        assert 'id' in self
        response = Server.find(self['id'])
        self.update(response)
        return self

    def modify(self, name=None, password=None):
        """Modify this Server's name or root password.

        :param name: Change the Server's name
        :type name: str
        :param password: Change the Server's root password
        :type password: str
        :returns: A modified CloudServers Server.
        :rtype: :class:`Server`

        .. versionadded:: 0.1
        """
        assert 'id' in self
        data = {'server': {}}
        if name is not None:
            data['server']['name'] = name
        if password is not None:
            data['server']['adminPass'] = password
        data = json.dumps(data)
        url = '/'.join([get_url('cloudservers'), 'servers', str(self['id'])])
        response = handle_request('put', url, data=data)
        if response:
            if name is not None:
                self['name'] = name
        return self

    def delete(self):
        """Delete this Server.

        .. warning::

            There is no confirmation step for this operation. When you delete a
            server it is permanent. If in doubt, create a backup image
            (:func:`vaporize.images.create`) first before deleting.

        .. versionadded:: 0.1
        """
        assert 'id' in self
        url = '/'.join([get_url('cloudservers'), 'servers', str(self['id'])])
        handle_request('delete', url)

    @property
    def ips(self):
        """
        Returns a list of public and private IPs for this Server.

        :returns: A list of public and private IPs for this Server.
        :rtype: A list of :class:`IP`

        .. versionadded:: 0.1
        """
        if 'addresses' not in self:
            assert 'id' in self
            url = '/'.join([get_url('cloudservers'), 'servers', str(self['id']),
                            'ips'])
            response = handle_request('get', url, wrapper=IP,
                                      container='addresses')
            self['addresses'] = response
        return self['addresses']

    @property
    def public_ips(self):
        """Returns the Server's Public IP.

        .. versionadded:: 0.1
        """
        if 'addresses' not in self:
            self['addresses'] = IP()
        if 'public' not in self['addresses']:
            assert 'id' in self
            url = '/'.join([get_url('cloudservers'), 'servers', str(self['id']),
                            'ips', 'public'])
            response = handle_request('get', url, wrapper=IP)
            self['addresses'].update(response)
        return self['addresses']['public']

    @property
    def private_ips(self):
        """Returns the Server's Private IP.

        .. versionadded:: 0.1
        """
        if 'addresses' not in self:
            self['addresses'] = IP()
        if 'private' not in self['addresses']:
            assert 'id' in self
            url = '/'.join([get_url('cloudservers'), 'servers', str(self['id']),
                            'ips', 'private'])
            response = handle_request('get', url, wrapper=IP)
            self['addresses'].update(response)
        return self['addresses']['private']

    def share_ip(self, address, ipgroup, configure=True):
        """Share this Server's IP in a Shared IP Group.

        :param address: IP to share in the Shared IP Group
        :type address: str
        :param ipgroup: A :class:`SharedIPGroup` or ``id``
        :type ipgroup: int or :class:`SharedIPGroup`
        :param configure: Configure the shared IP on the Server
        :type configure: bool
        :returns: The Shared IP Group associated with this Server.
        :rtype: :class:`SharedIPGroup`

        .. versionadded:: 0.1
        """
        assert 'id' in self
        if isinstance(ipgroup, SharedIPGroup):
            ipgroup = ipgroup.id
        ipgroup = int(ipgroup)
        data = json.dumps({'shareIp': {'sharedIpGroup': ipgroup,
                                       'configureServer': configure}})
        url = '/'.join([get_url('cloudservers'), 'servers', str(self['id']),
                        'ips', 'public', address])
        handle_request('put', url, data=data)

    def unshare_ip(self, address):
        """Unshare this Server's IP

        .. versionadded:: 0.1
        """
        assert 'id' in self
        url = '/'.join([get_url('cloudservers'), 'servers', str(self['id']),
                        'ips', 'public', address])
        handle_request('delete', url)

    def reboot(self, type='SOFT'):
        """Perform a soft/hard reboot on this Server.

        :param type: A reboot type (``SOFT`` or ``HARD``).
        :type type: str

        .. versionadded:: 0.1
        """
        assert 'id' in self, "Missing Server ID"
        assert type in ['SOFT', 'HARD'], "Reboot type must be 'SOFT' or 'HARD'"
        data = json.dumps({'reboot': {'type': type}})
        url = '/'.join([get_url('cloudservers'), 'servers',
                        str(self['id']), 'action'])
        handle_request('post', url, data)

    def rebuild(self, image):
        """Rebuild this Server using a specified Image

        :param image: The Image or ``id``
        :type image: int or :class:`Image`

        .. versionadded:: 0.1
        """
        assert 'id' in self
        if isinstance(image, Image):
            image = image.id
        image = int(image)
        data = json.dumps({'rebuild': {'imageId': int(image)}})
        url = '/'.join([get_url('cloudservers'), 'servers',
                        str(self['id']), 'action'])
        handle_request('post', url, data)

    def resize(self, flavor):
        """Resize this Server to a specific Flavor size

        :param flavor: The Flavor or ``id``
        :type flavor: int or :class:`Flavor`

        .. versionadded:: 0.1
        """
        assert 'id' in self
        if isinstance(flavor, Flavor):
            flavor = flavor.id
        flavor = int(flavor)
        data = json.dumps({'resize': {'flavorId': flavor}})
        url = '/'.join([get_url('cloudservers'), 'servers', str(self['id']),
                        'action'])
        handle_request('post', url, data)

    def confirm_resize(self):
        """Confirm a successful resize operation

        .. versionadded:: 0.1
        """
        assert 'id' in self
        data = json.dumps({'confirmResize': None})
        url = '/'.join([get_url('cloudservers'), 'servers',
                        str(self['id']), 'action'])
        handle_request('post', url, data)

    def revert_resize(self):
        """Revert an unsuccessful resize operation

        .. versionadded:: 0.1
        """
        assert 'id' in self
        data = json.dumps({'revertResize': None})
        url = '/'.join([get_url('cloudservers'), 'servers',
                        str(self['id']), 'action'])
        handle_request('post', url, data)

    @property
    def backup_schedule(self):
        """Return this Server's backup schedule

        :return: :class:`BackupSchedule`

        .. versionadded:: 0.1
        """
        if 'backup_schedule' not in self:
            assert 'id' in self
            url = '/'.join([get_url('cloudservers'), 'servers', str(self['id']),
                            'backup_schedule'])
            response = handle_request('get', url, wrapper=BackupSchedule,
                                      container='backupSchedule')
            self['backup_schedule'] = response
        return self['backup_schedule']

    @backup_schedule.setter
    def backup_schedule(self, schedule):
        """Enable a backup schedule for this Server

        :param schedule: A BackupSchedule instance 
        :type schedule: :class:`BackupSchedule`

            >>> server = vaporize.servers.Server.find(...)
            >>> bs = vaporize.servers.BackupSchedule.create(weekly=...,
            ...                                             daily=...)
            >>> server.backup_schedule = bs


        .. versionadded:: 0.1
        """
        assert 'id' in self
        assert isinstance(schedule, BackupSchedule)
        url = '/'.join([get_url('cloudservers'), 'servers', str(self['id']),
                        'backup_schedule'])
        handle_request('post', url, schedule.to_dict())
        self['backup_schedule'] = schedule

    @backup_schedule.deleter
    def backup_schedule(self):
        """Disable a backup schedule for this Server

            >>> server = vaporize.servers.Server.find(...)
            >>> del server.backup_schedule

        .. versionadded:: 0.1
        """
        assert 'id' in self
        url = '/'.join([get_url('cloudservers'), 'servers', str(self['id']),
                        'backup_schedule'])
        handle_request('delete', url)
        del self['backup_schedule']

    @classmethod
    def list(cls, limit=None, offset=None, detail=False):
        """
        List of CloudServer Servers

        :param limit: Limit the result set to a certain number
        :type limit: int
        :param offset: Offset the result set by a certain number
        :type offset: int
        :param detail: Return detailed information about each Server
        :type detail: bool
        :returns: A list of CloudServers Servers.
        :rtype: List of :class:`Server`

        .. versionadded:: 0.1
        """
        url = [get_url('cloudservers'), 'servers']
        if detail:
            url.append('detail')
        url = '/'.join(url)
        if limit is not None or offset is not None:
            url = query(url, limit=limit, offset=offset)
        return handle_request('get', url, wrapper=cls, container='servers')

    @classmethod
    def find(cls, id):
        """Return a Server using an ID

        :param id: The ``id`` of the Server to be retrieved
        :type id: int
        :return: A :class:`Server`

        .. versionadded:: 0.1
        """
        url = '/'.join([get_url('cloudservers'), 'servers', str(id)])
        return handle_request('get', url, wrapper=cls, container='server')

    @classmethod
    def create(cls, name, image, flavor, metadata=None, files=None):
        """Create a CloudServers Server

        :param name: A Server's name
        :type name: str
        :param image: An Image or ``id``
        :type image: int or :class:`Image`
        :param flavor: A Flavor or ``id``
        :type flavor: int or :class:`Flavor`
        :param metadata: Optional meta data to include with Server
        :type metadata: dict
        :param files: A list of files to load on Server
        :type files: dict
        :returns: A shiny new CloudServers Server.
        :rtype: :class:`Server`

        .. versionadded:: 0.1
        """
        if isinstance(image, Image):
            image = image.id
        image = int(image)
        if isinstance(flavor, Flavor):
            flavor = flavor.id
        flavor = int(flavor)
        data = {'server': {'name': name,
                           'imageId': image,
                           'flavorId': flavor,
                           'metadata': metadata or {},
                           'personality': []}}
        if isinstance(files, dict):
            for path, contents in list(files.items()):
                data['personality'].append({'path': path, 'contents': contents})
        data = json.dumps(data)
        url = '/'.join([get_url('cloudservers'), 'servers'])
        return handle_request('post', url, data, cls, 'server')


class SharedIPGroup(DotDict):
    """A Cloudservers Shared IP Group."""
    def __repr__(self):
        if 'name' in self:
            return '<SharedIPGroup %s>' % self['name']
        return super(SharedIPGroup, self).__repr__()

    def __setitem__(self, key, value):
        if key == 'sharedIpGroupId':
            key = 'id'
        elif key == 'configuredServer':
            key = 'configured'
        super(SharedIPGroup, self).__setitem__(key, value)

    def delete(self):
        """Delete this Shared IP Group.

        .. versionadded:: 0.1
        """
        assert 'id' in self
        url = '/'.join([get_url('cloudservers'), 'shared_ip_groups',
                        str(self['id'])])
        handle_request('delete', url)

    @classmethod
    def list(cls, limit=None, offset=None, detail=False):
        """Returns a list of Shared IP Groups.

        :param limit: Limit the result set by a certain number
        :type limit: int
        :param offset: Offset the result set by a certain number
        :type offset: int
        :param detail: Return additional details about each Shared IP Group
        :type detail: bool
        :returns: A list of Shared IP Groups.
        :rtype: A list of :class:`SharedIPGroup`

        .. versionadded:: 0.1
        """
        url = [get_url('cloudservers'), 'shared_ip_groups']
        if detail:
            url.append('detail')
        url = '/'.join(url)
        if limit is not None or offset is not None:
            url = query(url, limit=limit, offset=offset)
        return handle_request('get', url, wrapper=cls,
                              container='sharedIpGroups')

    @classmethod
    def find(cls, id):
        """Return a Shared IP Group by ID.

        :param id: The ID of the Shared IP Group to retrieve
        :type id: int
        :returns: A :class:`SharedIPGroup`

        .. versionadded:: 0.1
        """
        url = '/'.join([get_url('cloudservers'), 'shared_ip_groups', str(id)])
        return handle_request('get', url, wrapper=cls,
                              container='sharedIpGroup')

    @classmethod
    def create(cls, name, server):
        """Create a Shared IP Group.

        :param name: Name of the Shared IP Group
        :type name: str
        :param server: The Server or ``id`` to add to group
        :type server: int or :class:`Server`
        :returns: A shiny new CloudServers Shared IP Group.
        :rtype: :class:`SharedIPGroup`

        .. versionadded:: 0.1
        """
        if isinstance(server, Server):
            server = server.id
        server = int(server)
        data = {'sharedIpGroup': {'name': name,
                                  'server': server}}
        data = json.dumps(data)
        url = '/'.join([get_url('cloudservers'), 'server_ip_groups'])
        return handle_request('post', url, data, cls, 'sharedIpGroup')
