# Copyright 2013 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from ironic_python_agent import encoding


class RESTError(Exception, encoding.Serializable):
    """Base class for errors generated in ironic-python-client."""
    message = 'An error occurred'
    details = 'An unexpected error occurred. Please try back later.'
    status_code = 500
    serializable_fields = ('type', 'code', 'message', 'details')

    def __init__(self, *args, **kwargs):
        super(RESTError, self).__init__(*args, **kwargs)
        self.type = self.__class__.__name__
        self.code = self.status_code


class InvalidContentError(RESTError):
    """Error which occurs when a user supplies invalid content, either
    because that content cannot be parsed according to the advertised
    `Content-Type`, or due to a content validation error.
    """
    message = 'Invalid request body'
    status_code = 400

    def __init__(self, details):
        self.details = details


class NotFound(RESTError):
    """Error which occurs when a user supplies invalid content, either
    because that content cannot be parsed according to the advertised
    `Content-Type`, or due to a content validation error.
    """
    message = 'Not found'
    status_code = 404
    details = 'The requested URL was not found.'


class CommandExecutionError(RESTError):
    """Error raised when a command fails to execute."""

    message = 'Command execution failed'

    def __init__(self, details):
        super(CommandExecutionError, self).__init__()
        self.details = details


class InvalidCommandError(InvalidContentError):
    """Error which is raised when an unknown command is issued."""

    messsage = 'Invalid command'

    def __init__(self, details):
        super(InvalidCommandError, self).__init__(details)


class InvalidCommandParamsError(InvalidContentError):
    """Error which is raised when command parameters are invalid."""

    message = 'Invalid command parameters'

    def __init__(self, details):
        super(InvalidCommandParamsError, self).__init__(details)


class RequestedObjectNotFoundError(NotFound):
    def __init__(self, type_descr, obj_id):
        details = '{0} with id {1} not found.'.format(type_descr, obj_id)
        super(RequestedObjectNotFoundError, self).__init__(details)
        self.details = details


class IronicAPIError(RESTError):
    """Error raised when a call to the agent API fails."""

    message = 'Error in call to ironic-api.'

    def __init__(self, details):
        super(IronicAPIError, self).__init__(details)
        self.details = details


class HeartbeatError(IronicAPIError):
    """Error raised when a heartbeat to the agent API fails."""

    message = 'Error heartbeating to agent API.'

    def __init__(self, details):
        super(HeartbeatError, self).__init__(details)


class LookupNodeError(IronicAPIError):
    """Error raised when the node configuration lookup to the Ironic API
    fails.
    """

    message = 'Error getting configuration from Ironic.'

    def __init__(self, details):
        super(LookupNodeError, self).__init__(details)


class LookupAgentIPError(IronicAPIError):
    """Error raised when automatic IP lookup fails."""

    message = 'Error finding IP for Ironic Agent'

    def __init__(self, details):
        super(IronicAPIError, self).__init__(details)


class LookupAgentInterfaceError(IronicAPIError):
    """Error raised when agent interface lookup fails."""

    message = 'Error finding network interface for Ironic Agent'

    def __init__(self, details):
        super(IronicAPIError, self).__init__(details)


class ImageDownloadError(RESTError):
    """Error raised when an image cannot be downloaded."""

    message = 'Error downloading image.'

    def __init__(self, image_id):
        super(ImageDownloadError, self).__init__()
        self.details = 'Could not download image with id {0}.'.format(image_id)


class ImageChecksumError(RESTError):
    """Error raised when an image fails to verify against its checksum."""

    message = 'Error verifying image checksum.'

    def __init__(self, image_id):
        super(ImageChecksumError, self).__init__()
        self.details = 'Image with id {0} failed to verify against checksum.'
        self.details = self.details.format(image_id)


class ImageWriteError(RESTError):
    """Error raised when an image cannot be written to a device."""

    message = 'Error writing image to device.'

    def __init__(self, device, exit_code, stdout, stderr):
        super(ImageWriteError, self).__init__()
        self.details = ('Writing image to device {0} failed with exit code '
                        '{1}. stdout: {2}. stderr: {3}')
        self.details = self.details.format(device, exit_code, stdout, stderr)


class ConfigDriveTooLargeError(RESTError):
    """Error raised when a configdrive is larger than the partition."""
    message = 'Configdrive is too large for intended partition.'

    def __init__(self, filename, filesize):
        details = ('Configdrive at {0} has size {1}, which is larger than '
                   'the intended partition.').format(filename, filesize)
        super(ConfigDriveTooLargeError, self).__init__(details)
        self.details = details


class ConfigDriveWriteError(RESTError):
    """Error raised when a configdrive directory cannot be written to a
    device.
    """

    message = 'Error writing configdrive to device.'

    def __init__(self, device, exit_code, stdout, stderr):
        details = ('Writing configdrive to device {0} failed with exit code '
                   '{1}. stdout: {2}. stderr: {3}.')
        details = details.format(device, exit_code, stdout, stderr)
        super(ConfigDriveWriteError, self).__init__(details)
        self.details = details


class SystemRebootError(RESTError):
    """Error raised when a system cannot reboot."""

    message = 'Error rebooting system.'

    def __init__(self, exit_code, stdout, stderr):
        super(SystemRebootError, self).__init__()
        self.details = ('Reboot script failed with exit code {0}. stdout: '
                        '{1}. stderr: {2}.')
        self.details = self.details.format(exit_code, stdout, stderr)


class BlockDeviceEraseError(RESTError):
    """Error raised when an error occurs erasing a block device."""

    message = 'Error erasing block device'

    def __init__(self, details):
        super(BlockDeviceEraseError, self).__init__(details)
        self.details = details


class BlockDeviceError(RESTError):
    """Error raised when a block devices causes an unknown error"""
    message = 'Block device caused unknown error'

    def __init__(self, details):
        super(BlockDeviceError, self).__init__(details)
        self.details = details


class VirtualMediaBootError(RESTError):
    """Error raised when booting ironic-python-client from virtual media
    fails.
    """
    message = 'Booting ironic-python-client from virtual media failed.'

    def __init__(self, details):
        super(RESTError, self).__init__(details)
        self.details = details


class ExtensionError(Exception):
    pass


class UnknownNodeError(Exception):
    """Error raised when the agent is not associated with an Ironic node."""

    message = 'Agent is not associated with an Ironic node.'

    def __init__(self, message=None):
        if message is not None:
            self.message = message
        super(UnknownNodeError, self).__init__(self.message)