from contextlib import contextmanager

from parallels.core import safe_format, get_logger, messages
from parallels.core.runners.web.utils.stream_downloader.packet.error import PacketError
from parallels.core.runners.web.utils.stream_downloader.packet.proc_end import PacketProcEnd
from parallels.core.runners.web.utils.stream_downloader.packet.stderr_chunk import PacketStderrChunk
from parallels.core.runners.web.utils.stream_downloader.packet.stdout_chunk import PacketStdoutChunk
from parallels.core.runners.web.utils.stream_downloader.packet_type import PacketType
from parallels.core.runners.web.utils.stream_downloader.stream_packet_readers.base_stream_packet_reader import \
    BaseStreamPacketReader
from parallels.core.utils.json_utils import decode_json

logger = get_logger(__name__)


class PlainStreamPacketReader(BaseStreamPacketReader):
    # maximum length of data which will be read from agent if unexpected data received
    ERROR_DATA_LEN = 1024*10

    def iter_packets(self):
        while True:
            self.reset_collected_data()

            packet_type = self._read_line()
            # check for stream end
            if packet_type == '':
                break
            with self.error_handler(messages.PLAIN_STREAM_PACKET_READER_DATA_TYPE_PACKET_TYPE):
                packet_type = int(packet_type)
            if packet_type == PacketType.STDOUT_CHUNK:
                with self.error_handler(messages.PLAIN_STREAM_PACKET_READER_DATA_TYPE_STDOUT_CHUNK):
                    length = int(self._read_line())
                    data = self._read_bytes(length)
                    packet = PacketStdoutChunk(data)
            elif packet_type == PacketType.STDERR_CHUNK:
                with self.error_handler(messages.PLAIN_STREAM_PACKET_READER_DATA_TYPE_STDERR_CHUNK):
                    length = int(self._read_line())
                    data = self._read_bytes(length)
                    packet = PacketStderrChunk(data)
            elif packet_type == PacketType.ERROR:
                with self.error_handler(messages.PLAIN_STREAM_PACKET_READER_DATA_TYPE_ERROR_PACKET):
                    message_length = int(self._read_line())
                    message = self._read_bytes(message_length)
                    packet = PacketError(message)
            elif packet_type == PacketType.PROC_END:
                with self.error_handler(messages.PLAIN_STREAM_PACKET_READER_DATA_TYPE_FINAL_PACKET):
                    exit_code = int(self._read_line())
                    packet = PacketProcEnd(exit_code)
            else:
                raise Exception(messages.PLAIN_STREAM_PACKET_READER_INVALID_PACKET_TYPE % packet_type)

            yield packet

    @contextmanager
    def error_handler(self, data_type):
        try:
            yield
        except Exception as e:
            logger.exception()

            error_data = self.collected_data
            len_to_read = self.ERROR_DATA_LEN - len(self.collected_data)
            if len_to_read > 0:
                error_data += self._read(len_to_read)
            received_data = messages.PLAIN_STREAM_PACKET_READER_RECEIVED_DATA
            if len(error_data) > self.ERROR_DATA_LEN:
                error_data = error_data[:self.ERROR_DATA_LEN]
                received_data = messages.PLAIN_STREAM_PACKET_READER_RECEIVED_DATA_CUT % self.ERROR_DATA_LEN

            error_message = safe_format(
                messages.PLAIN_STREAM_PACKET_READER_UNEXPECTED_DATA_RECEIVED,
                data_type=data_type, received_data=received_data, error_data=error_data, reason=e
            )

            try:
                decoded_error_data = decode_json(error_data)
            except Exception:
                raise Exception(error_message)
            if isinstance(decoded_error_data, dict) and 'error' in decoded_error_data:
                raise Exception(decoded_error_data['error'])
            raise Exception(error_message)
