from ..Dependencies import *
from .reasons import *

class Context:
    def __init__(app): ...

    @classmethod
    def _r(app):
        return current_task().__BlazeioProtocol__

    @classmethod
    def r_sync(app):
        return app._r()

    @classmethod
    async def r(app):
        return app._r()

    @classmethod
    async def from_task(app, task):
        return task.get_coro().cr_frame.f_locals.get("app")
        
    @classmethod
    def is_prot(app, r):
        return "Blazeio" in r.__class__.__name__

class ClientContext:
    def __init__(app): ...

    @classmethod
    def r(app):
        return current_task().__BlazeioClientProtocol__

    @classmethod
    def is_prot(app, r):
        return "Blazeio" in r.__class__.__name__

class Prepare:
    def __init__(app): ...

    @classmethod
    async def text(app, headers: dict={}, status:int = 206, content_type: str = "text/plain; charset=utf-8", chunked: bool = False):
        r = Context.r_sync()
        headers = dict(headers)
        headers["Content-type"] = content_type
        if chunked:
            headers["Transfer-encoding"] = "chunked"
            status = 200 if status == 206 else status

        await r.prepare(headers, status)

    @classmethod
    async def json(app, headers: dict={}, status:int = 206, content_type: str = "application/json; charset=utf-8", chunked: bool = False):
        r = Context.r_sync()
        headers = dict(headers)
        headers["Content-type"] = content_type
        if chunked:
            headers["Transfer-encoding"] = "chunked"
            status = 200 if status == 206 else status

        await r.prepare(headers, status)

class __Deliver__:
    content_types = {
        "text": "text/plain; charset=utf-8",
        "json": "application/json; charset=utf-8",
    }

    def __init__(app): ...

    async def deliver(app, r, data: (str, bytes, bytearray, memoryview, None, dict) = None, status: int = 200, headers: (dict, None) = None, path: (str, None) = None, content_type: (str, None) = None, indent: int = 0):
        headers = headers or {}
        if isinstance(data, str):
            data = data.encode()
        elif isinstance(data, bytearray):
            data = bytes(data)
        elif isinstance(data, memoryview):
            data = bytes(data)
        elif isinstance(data, (dict, list)):
            data = dumps(data, indent=indent).encode()
        
        if not "Content-type" in headers and not "Content-Type" in headers and not "content-type" in headers:
            headers["Content-type"] = content_type

        if data:
            if not "Content-length" in headers and not "Content-Length" in headers and not "content-length" in headers:
                headers["Content-length"] = str(len(data))

        if path:
            status = 302 if (status < 300 and status > 310)  else status
            
            if not "Location" in headers and not "location" in headers:
                headers["Location"] = path

        await r.prepare(headers, status)
        await r.write(data)

    def __getattr__(app, name):
        def method(*a, **kw):
            if not Context.is_prot(a[0]):
                a = (Context.r_sync(), *a)

            return app.deliver(*a, **kw, content_type = app.content_types.get(name, name.replace("_", "/")))

        setattr(app, name, method)
        return method

Deliver = __Deliver__()

class Abort(BlazeioException):
    __slots__ = ('args', 'r', 'kwargs')
    def __init__(app, *args, **kwargs):
        app.args = args
        app.kwargs = kwargs
        app.r = None

    def text(app, r = None):
        app.r = r or Context.r_sync()
        message = (app.args[0] if len(app.args) >= 1 else "Something went wrong").encode()
        status = app.args[1] if len(app.args) >= 2 else 403
        headers = app.args[2] if len(app.args) >= 3 else app.kwargs

        return Deliver.text(app.r, memoryview(message), status, headers or {})

class NotFoundErr(Abort):
    __slots__ = ()

class Eof(BlazeioException):
    __slots__ = ()
    def __init__(app, *args): pass

class __Payload__:
    __slots__ = ()
    def __init__(app): pass

    def __getattr__(app, name):
        return getattr(current_task().get_coro().cr_frame.f_locals.get("app"), name)

class FileIO:
    @classmethod
    async def save(app, file_path: str, mode: str = "wb", r=None):
        if not r: r = Context.r_sync()
        async with async_open(file_path, mode) as f:
            async for chunk in r.pull():
                if chunk: await f.write(chunk)

class Enterchunked:
    __slots__ = ("a", "k")

    def __init__(app, *a, **k):
        if not a or not isinstance(a[0], BlazeioProtocol):
            a = (Context.r_sync(), *a)

        if len(a) < 2:
            a = (*a, {})

        app.a, app.k = a, k

    async def __aenter__(app):
        app.a[1]["transfer-encoding"] = "chunked"
        await app.a[0].prepare(*app.a[1:], **app.k)
        return app

    async def __aexit__(app, exc_type=None, exc_value=None, traceback=None):
        await app.a[0].eof()

class Protocols:
    @classmethod
    async def forward_to_client(app):
        client, server = Context._r(), ClientContext.r()
        async for chunk in server.pull():
            await client.write(chunk)

    @classmethod
    async def forward_to_server(app):
        client, server = Context._r(), ClientContext.r()
        async for chunk in client.pull():
            await server.write(chunk)

if __name__ == "__main__":
    pass
