import json
import re
import aiohttp
import codecs
from darabonba.event import Event

from io import BytesIO, StringIO
from typing import Any, BinaryIO, Generator, AsyncGenerator, Dict

# define WRITEABLE
sse_line_pattern = re.compile('(?P<name>[^:]*):?( ?(?P<value>.*))?')

class BaseStream:
    def __init__(self, size=1024):
        self.size = size

    def read(self, size=1024):
        raise NotImplementedError('read method must be overridden')

    def __len__(self):
        raise NotImplementedError('__len__ method must be overridden')

    def __next__(self):
        raise NotImplementedError('__next__ method must be overridden')

    def __iter__(self):
        return self


class _ReadableMc(type):
    def __instancecheck__(self, instance):
        if hasattr(instance, 'read') and hasattr(instance, '__iter__'):
            return True


class READABLE(metaclass=_ReadableMc):
    pass

class SyncSSEResponseWrapper:
    def __init__(self, session, response):
        self.session = session
        self.response = response
        self._closed = False
    
    def close(self):
        if not self._closed:
            self.response.close()
            self.session.close()
            self._closed = True
    
    def __iter__(self):
        return self._read_chunks()
    
    def _read_chunks(self):
        try:
            for chunk in self.response.iter_content(chunk_size=8192):
                yield chunk
        finally:
            self.close()
    
    def read(self) -> bytes:
        try:
            return self.response.content
        finally:
            self.close()

class SSEResponseWrapper:
    def __init__(self, session: aiohttp.ClientSession, response: aiohttp.ClientResponse):
        self.session = session
        self.response = response
        self._closed = False
        self._content_cache = None
    
    async def close(self):
        if not self._closed:
            self.response.close()
            await self.session.close()
            self._closed = True
    
    def __aiter__(self):
        return self._read_chunks()
    
    async def _read_chunks(self):
        try:
            async for chunk in self.response.content.iter_chunked(8192):
                yield chunk
        finally:
            await self.close()
    
    async def read(self) -> bytes:
        if self._content_cache is not None:
            return self._content_cache
        
        try:
            content = await self.response.read()
            self._content_cache = content
            return content
        finally:
            await self.close()

class _WriteableMc(type):
    def __instancecheck__(self, instance):
        if hasattr(instance, 'write'):
            return True


class WRITABLE(metaclass=_WriteableMc):
    pass


STREAM_CLASS = (READABLE, WRITABLE)

class Stream:

    def __init__(self, data=None):
        self.data = data if data is not None else b''
        self.position = 0

    @staticmethod
    def __read_part(f, size=1024):
        while True:
            part = f.read(size)
            if part:
                yield part
            else:
                return

    @staticmethod
    def __to_string(
        val: bytes,
    ) -> str:
        """
        Convert a bytes to string(utf8)
        @return: the return string
        """
        if isinstance(val, str):
            return val
        elif isinstance(val, bytes):
            return val.decode('utf-8')
        else:
            return str(val)

    @staticmethod
    def __parse_json(
        val: str,
    ) -> Any:
        """
        Parse it by JSON format
        @return: the parsed result
        """
        try:
            return json.loads(val)
        except ValueError:
            raise RuntimeError(f'Failed to parse the value as json format, Value: "{val}".')

    @staticmethod
    def read_as_bytes(stream) -> bytes:
        """
        Read data from a readable stream, and compose it to a bytes
        @param stream: the readable stream
        @return: the bytes result
        """
        if isinstance(stream, SyncSSEResponseWrapper):
            return stream.read()
        elif isinstance(stream, READABLE):
            b = b''
            for part in Stream.__read_part(stream, 1024):
                b += part
            return b
        elif isinstance(stream, bytes):
            return stream
        else:
            return bytes(stream, encoding='utf-8')
    
    @staticmethod
    async def read_as_bytes_async(stream) -> bytes:
        """
        Read data from a readable stream, and compose it to a bytes
        @param stream: the readable stream
        @return: the bytes result
        """
        if isinstance(stream, bytes):
            return stream
        elif isinstance(stream, str):
            return bytes(stream, encoding='utf-8')
        else:
            return await stream.read()
    
    @staticmethod
    def read_as_json(stream) -> Any:
        """
        Read data from a readable stream, and parse it by JSON format
        @param stream: the readable stream
        @return: the parsed result
        """
        return Stream.__parse_json(Stream.read_as_string(stream))

    @staticmethod
    async def read_as_json_async(stream) -> Any:
        """
        Read data from a readable stream, and parse it by JSON format
        @param stream: the readable stream
        @return: the parsed result
        """
        return Stream.__parse_json(
            await Stream.read_as_string_async(stream)
        )


    @staticmethod
    def read_as_string(stream) -> str:
        """
        Read data from a readable stream, and compose it to a string
        @param stream: the readable stream
        @return: the string result
        """
        buff = Stream.read_as_bytes(stream)
        return Stream.__to_string(buff)
    
    @staticmethod
    async def read_as_string_async(stream) -> str:
        """
        Read data from a readable stream, and compose it to a string
        @param stream: the readable stream
        @return: the string result
        """
        buff = await Stream.read_as_bytes_async(stream)
        return Stream.__to_string(buff)
    
    @staticmethod
    def read_as_sse(stream) -> Generator[Event, None, None]:
        """
        Read events from SSE stream (synchronous version)
        """
        if isinstance(stream, SyncSSEResponseWrapper):
            for event in Stream._parse_sse_stream_sync(stream):
                yield Event(
                    id=event.get('id'),
                    data=event.get('data'),
                    event=event.get('event'),
                    retry=event.get('retry'))
        elif hasattr(stream, 'iter_content'):
            # Read directly from the content stream of requests response object
            for event in Stream._parse_sse_stream_from_response_sync(stream):
                yield Event(
                    id=event.get('id'),
                    data=event.get('data'),
                    event=event.get('event'),
                    retry=event.get('retry'))
        else:
            for event in Stream._parse_sse_stream_sync(stream):
                yield Event(
                    id=event.get('id'),
                    data=event.get('data'),
                    event=event.get('event'),
                    retry=event.get('retry'))

    @staticmethod
    async def read_as_sse_async(stream) -> AsyncGenerator[Event, None]:
        """
        Read events from SSE stream
        """
        if isinstance(stream, SSEResponseWrapper):
            async for event in Stream._parse_sse_stream(stream):
                yield Event(
                    id = event.get('id'),
                    data = event.get('data'),
                    event= event.get('event'),
                    retry = event.get('retry'))
        elif hasattr(stream, 'content'):
            # Read directly from the content stream of aiohttp response object
            async for event in Stream._parse_sse_stream_from_response(stream):
                yield Event(
                    id = event.get('id'),
                    data = event.get('data'),
                    event= event.get('event'),
                    retry = event.get('retry'))
        else:
            async for event in Stream._parse_sse_stream(stream):
                yield Event(
                    id = event.get('id'),
                    data = event.get('data'),
                    event= event.get('event'),
                    retry = event.get('retry'))

    def read(self, size=None):
        if size is None:
            return self.data[self.position:]
        
        start = self.position
        end = min(start + size, len(self.data))
        self.position = end
        return self.data[start:end]

    def write(self, data):
        if isinstance(data, (bytes, str)):
            self.data = data
        else:
            raise TypeError("Data should be bytes or string.")

    def pipe(self, output_stream, buffer_size=1024):
        if not isinstance(output_stream, Stream):
            raise TypeError("Output stream should be an instance of Stream.")
        
        while True:
            chunk = self.read(buffer_size)
            if not chunk:
                break
            output_stream.write(chunk)
    
    @staticmethod
    def to_readable(
        value: Any,
    ) -> BinaryIO:
        """
        Assert a value, if it is a readable, return it, otherwise throws
        @return: the readable value
        """
        if isinstance(value, str):
            value = value.encode('utf-8')

        if isinstance(value, bytes):
            value = BytesIO(value)
        elif not isinstance(value, READABLE):
            raise ValueError(f'The value is not a readable')
        return value

    @staticmethod
    def to_writeable(
        value: Any,
    ) -> WRITABLE:
        """
        Assert a value, if it is a writeable, return it, otherwise throws
        @return: the writeable value
        """
        if isinstance(value, str):
            value = StringIO(value)

        elif isinstance(value, bytes):
            value = BytesIO(value)
        elif not isinstance(value, WRITABLE):
            raise ValueError(f'The value is not a writeable')
        return value
    
    @staticmethod
    async def _parse_sse_stream(wrapper: SSEResponseWrapper) -> AsyncGenerator[Dict[str, Any], None]:
        """
        Analyze SSE stream data
        """
        buffer = ""
        current_event = Event()
        
        MAX_BUFFER_SIZE = 1024 * 1024  # 1MB
        dec = codecs.getincrementaldecoder('utf-8')()
        
        async for chunk in wrapper:
            try:
                chunk_str = dec.decode(chunk)
            except UnicodeDecodeError:
                chunk_str = chunk.decode('utf-8', errors='replace')
            
            if len(buffer) + len(chunk_str) > MAX_BUFFER_SIZE:
                import logging
                logging.warning("SSE stream data too large, skipping chunk")
                continue
                
            buffer += chunk_str

            while '\n' in buffer:
                line, buffer = buffer.split('\n', 1)
                line = line.rstrip('\r')  # Remove \r
                
                if not line.strip():
                    if current_event.data is not None:
                        yield {
                            'id': current_event.id,
                            'event': current_event.event or 'message',
                            'data': current_event.data,
                            'retry': current_event.retry
                        }
                        current_event = Event()
                    continue
                
                if line.startswith(':'):
                    continue
                
                if ':' in line:
                    match = sse_line_pattern.match(line)
                    if match:
                        name = match.group('name').strip()
                        value = match.group('value').strip()
                        
                        if name == 'event':
                            current_event.event = value
                        elif name == 'id':
                            current_event.id = value
                        elif name == 'data':
                            if current_event.data is None:
                                current_event.data = value
                            else:
                                current_event.data += '\n' + value
                        elif name == 'retry':
                            try:
                                current_event.retry = int(value)
                            except ValueError:
                                pass
                else:
                    if current_event.data is None:
                        current_event.data = line
                    else:
                        current_event.data += '\n' + line

        if buffer.strip() and current_event.data is not None:
            yield {
                'id': current_event.id,
                'event': current_event.event or 'message',
                'data': current_event.data,
                'retry': current_event.retry
            }

    @staticmethod
    async def _parse_sse_stream_from_response(response) -> AsyncGenerator[Dict[str, Any], None]:
        buffer = ""
        current_event = Event()

        async for chunk in response.content.iter_chunked(8192):
            try:
                chunk_str = chunk.decode('utf-8')
            except UnicodeDecodeError:
                continue
            
            buffer += chunk_str
            
            while '\n' in buffer:
                line, buffer = buffer.split('\n', 1)
                line = line.rstrip('\r')
                
                if not line.strip():
                    if current_event.data is not None:
                        yield {
                            'id': current_event.id,
                            'event': current_event.event or 'message',
                            'data': current_event.data,
                            'retry': current_event.retry
                        }
                        current_event = Event()
                    continue
                
                if line.startswith(':'):
                    continue
                
                if ':' in line:
                    match = sse_line_pattern.match(line)
                    if match:
                        name = match.group('name').strip()
                        value = match.group('value').strip()
                        
                        if name == 'event':
                            current_event.event = value
                        elif name == 'id':
                            current_event.id = value
                        elif name == 'data':
                            if current_event.data is None:
                                current_event.data = value
                            else:
                                current_event.data += '\n' + value
                        elif name == 'retry':
                            try:
                                current_event.retry = int(value)
                            except ValueError:
                                pass
                else:
                    if current_event.data is None:
                        current_event.data = line
                    else:
                        current_event.data += '\n' + line

        if buffer.strip() and current_event.data is not None:
            yield {
                'id': current_event.id,
                'event': current_event.event or 'message',
                'data': current_event.data,
                'retry': current_event.retry
            }
    
    @staticmethod
    def _parse_sse_stream_sync(wrapper: SyncSSEResponseWrapper) -> Generator[Dict[str, Any], None, None]:
        """
        Analyze SSE stream data (synchronous version)
        """
        buffer = ""
        current_event = Event()

        for chunk in wrapper:
            # Decoding byte data into strings
            try:
                chunk_str = chunk.decode('utf-8')
            except UnicodeDecodeError:
                # If decoding fails, skip this chunk
                continue
            
            buffer += chunk_str
            
            # Split processing by row
            while '\n' in buffer:
                line, buffer = buffer.split('\n', 1)
                line = line.rstrip('\r')  # Remove \r
                
                if not line.strip():
                    if current_event.data is not None:
                        yield {
                            'id': current_event.id,
                            'event': current_event.event or 'message',
                            'data': current_event.data,
                            'retry': current_event.retry
                        }
                        current_event = Event()
                    continue
                
                # Skip comment lines
                if line.startswith(':'):
                    continue
                
                if ':' in line:
                    match = sse_line_pattern.match(line)
                    if match:
                        name = match.group('name').strip()
                        value = match.group('value').strip()
                        
                        if name == 'event':
                            current_event.event = value
                        elif name == 'id':
                            current_event.id = value
                        elif name == 'data':
                            if current_event.data is None:
                                current_event.data = value
                            else:
                                current_event.data += '\n' + value
                        elif name == 'retry':
                            try:
                                current_event.retry = int(value)
                            except ValueError:
                                pass
                else:
                    if current_event.data is None:
                        current_event.data = line
                    else:
                        current_event.data += '\n' + line

        if buffer.strip() and current_event.data is not None:
            yield {
                'id': current_event.id,
                'event': current_event.event or 'message',
                'data': current_event.data,
                'retry': current_event.retry
            }

    @staticmethod
    def _parse_sse_stream_from_response_sync(response) -> Generator[Dict[str, Any], None, None]:
        """
        Parse SSE stream from requests response object (synchronous version)
        """
        buffer = ""
        current_event = Event()

        for chunk in response.iter_content(chunk_size=8192):
            try:
                chunk_str = chunk.decode('utf-8')
            except UnicodeDecodeError:
                continue
            
            buffer += chunk_str
            
            while '\n' in buffer:
                line, buffer = buffer.split('\n', 1)
                line = line.rstrip('\r')
                
                if not line.strip():
                    if current_event.data is not None:
                        yield {
                            'id': current_event.id,
                            'event': current_event.event or 'message',
                            'data': current_event.data,
                            'retry': current_event.retry
                        }
                        current_event = Event()
                    continue
                
                if line.startswith(':'):
                    continue
                
                if ':' in line:
                    match = sse_line_pattern.match(line)
                    if match:
                        name = match.group('name').strip()
                        value = match.group('value').strip()
                        
                        if name == 'event':
                            current_event.event = value
                        elif name == 'id':
                            current_event.id = value
                        elif name == 'data':
                            if current_event.data is None:
                                current_event.data = value
                            else:
                                current_event.data += '\n' + value
                        elif name == 'retry':
                            try:
                                current_event.retry = int(value)
                            except ValueError:
                                pass
                else:
                    if current_event.data is None:
                        current_event.data = line
                    else:
                        current_event.data += '\n' + line

        if buffer.strip() and current_event.data is not None:
            yield {
                'id': current_event.id,
                'event': current_event.event or 'message',
                'data': current_event.data,
                'retry': current_event.retry
            }