from dataclasses import dataclass
from pathlib import Path

from aiofiles import open, os

from not_my_ex.mime import mime_for


class ImageTooBigError(Exception):
    pass


@dataclass
class Media:
    path: str | None
    content: bytes
    mime: str
    alt: str | None = None

    @classmethod
    async def from_img(
        cls, img: str, alt: str | None = None, limit: int | None = None
    ) -> "Media":
        if not await os.path.exists(img):
            raise ValueError(f"File {img} does not exist")

        if limit and Path(img).stat().st_size > limit:
            raise ImageTooBigError(f"{img} is larger than {limit} bytes")

        async with open(img, "rb") as handler:
            contents = await handler.read()

        mime = mime_for(img, contents)
        if not isinstance(mime, str):
            raise ValueError(f"Could not guess mime type for {img}")

        return cls(img, contents, mime, alt)

    def check_alt_text(self):
        while self.path and not self.alt:
            alt = input(f"Enter an alt text for {self.path}: ")
            self.alt = alt.strip() or None

    async def dimensions(self) -> tuple[int, int] | None:
        if not self.path:
            return None

        if self.mime == "image/png":
            async with open(self.path, "rb") as reader:
                await reader.seek(16)
                width_bytes = await reader.read(4)
                height_bytes = await reader.read(4)
                if len(width_bytes) == 4 and len(height_bytes) == 4:
                    width = int.from_bytes(width_bytes, "big")
                    height = int.from_bytes(height_bytes, "big")
                    return width, height

        if self.mime == "image/jpeg":
            async with open(self.path, "rb") as reader:
                while True:
                    marker = await reader.read(2)
                    if not marker or len(marker) < 2:
                        break

                    if marker == b"\xff\xd8":
                        continue

                    if marker in (b"\xff\xc0", b"\xff\xc2"):
                        length_bytes = await reader.read(2)
                        if not length_bytes or len(length_bytes) < 2:
                            break

                        length = int.from_bytes(length_bytes, "big") - 2
                        if length < 0:
                            break

                        await reader.read(1)
                        height_bytes = await reader.read(2)
                        width_bytes = await reader.read(2)
                        if len(height_bytes) == 2 and len(width_bytes) == 2:
                            height = int.from_bytes(height_bytes, "big")
                            width = int.from_bytes(width_bytes, "big")
                            return width, height
                        break

                    else:
                        length_bytes = await reader.read(2)
                        if not length_bytes or len(length_bytes) < 2:
                            break

                        length = int.from_bytes(length_bytes, "big") - 2
                        if length < 0:
                            break

                        await reader.seek(length, 1)

        return None
