"""Python 3.10+ client. Uses only the standard library; no credentials are embedded."""
import hashlib
import http.client
import json
import mimetypes
import os
import pathlib
import urllib.parse
import uuid


class LibraryError(Exception):
    def __init__(self, status, body):
        error = body.get("error", {}) if isinstance(body, dict) else {}
        self.status, self.code = status, error.get("code")
        super().__init__(error.get("message", f"HTTP {status}"))


class MediaLibrary:
    def __init__(self, base_url="https://product-film-library-mxtec.pages.dev", api_key=None):
        self.base_url = base_url.rstrip("/")
        self.api_key = api_key

    def request(self, method, endpoint, data=None, headers=None, stream=None):
        url = urllib.parse.urlsplit(self.base_url)
        connection = (http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection)(url.netloc, timeout=120)
        hdr = dict(headers or {})
        if self.api_key:
            hdr["Authorization"] = "Bearer " + self.api_key
        payload = stream
        if data is not None:
            payload = json.dumps(data, ensure_ascii=False).encode("utf-8")
            hdr["Content-Type"] = "application/json"
        try:
            connection.request(method, url.path + "/api/v1/" + endpoint, body=payload, headers=hdr)
            response = connection.getresponse()
            raw = response.read()
            result = json.loads(raw) if raw else {}
            if response.status >= 400:
                raise LibraryError(response.status, result)
            if response.getheader("ETag"):
                result["etag"] = response.getheader("ETag")
            return result
        finally:
            connection.close()

    def list(self, **params):
        return self.request("GET", "assets?" + urllib.parse.urlencode({k: v for k, v in params.items() if v is not None}))

    def iterate(self, **params):
        page = 1
        while True:
            result = self.list(**{**params, "page": page, "page_size": params.get("page_size", 100)})
            yield from result["data"]
            if not result["links"]["next"]:
                return
            page += 1

    def get(self, asset_id):
        return self.request("GET", "assets/" + urllib.parse.quote(asset_id, safe=""))

    def update(self, asset_id, patch, etag):
        return self.request("PATCH", "assets/" + urllib.parse.quote(asset_id, safe=""), patch, {"If-Match": etag})

    def archive(self, asset_id, etag):
        return self.request("DELETE", "assets/" + urllib.parse.quote(asset_id, safe=""), headers={"If-Match": etag})

    def restore(self, asset_id, etag):
        return self.request("POST", "assets/" + urllib.parse.quote(asset_id, safe="") + "/restore", headers={"If-Match": etag})

    def upload(self, filename, metadata, mime_type=None, idempotency_key=None):
        path = pathlib.Path(filename)
        if path.stat().st_size > 94371840:
            raise ValueError("Maximum upload size is 90 MiB")
        sha = hashlib.sha256()
        with path.open("rb") as source:
            for chunk in iter(lambda: source.read(1024 * 1024), b""):
                sha.update(chunk)
        mime = mime_type or mimetypes.guess_type(path.name)[0] or "application/octet-stream"
        content = {**metadata, "file": {"name": path.name, "mime_type": mime, "size_bytes": path.stat().st_size, "sha256": sha.hexdigest()}}
        created = self.request("POST", "assets", content, {"Idempotency-Key": idempotency_key or str(uuid.uuid4())})
        if created["data"]["status"] == "archived":
            raise LibraryError(409, {"error": {"code": "duplicate_archived", "message": "Same file is archived; restore it first."}})
        if not created.get("upload"):
            return created
        asset_id = created["data"]["id"]
        try:
            with path.open("rb") as source:
                return self.request("PUT", "assets/" + asset_id + "/content", headers={"Content-Type": mime, "Content-Length": str(path.stat().st_size)}, stream=source)
        except (OSError, http.client.HTTPException):
            return self.request("POST", "assets/" + asset_id + "/complete")

    def download(self, asset_id, filename):
        url = urllib.parse.urlsplit(self.base_url)
        connection = (http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection)(url.netloc, timeout=120)
        headers = {"Authorization": "Bearer " + self.api_key} if self.api_key else {}
        destination = pathlib.Path(filename)
        temporary = destination.with_name(destination.name + ".part-" + uuid.uuid4().hex)
        expected = self.get(asset_id)["data"]["file"]["sha256"]
        digest = hashlib.sha256()
        try:
            connection.request("GET", url.path + "/api/v1/assets/" + urllib.parse.quote(asset_id, safe="") + "/download", headers=headers)
            response = connection.getresponse()
            if response.status != 200:
                raise LibraryError(response.status, json.loads(response.read()))
            with temporary.open("xb") as output:
                while chunk := response.read(1024 * 1024):
                    digest.update(chunk)
                    output.write(chunk)
            if digest.hexdigest() != expected:
                raise ValueError("Downloaded SHA-256 mismatch")
            if destination.exists():
                raise FileExistsError(destination)
            temporary.rename(destination)
        finally:
            connection.close()
            temporary.unlink(missing_ok=True)
