import os

import aiohttp
from lxml import etree


class AppStreamClient:

    def __init__(self, session: aiohttp.ClientSession, appstream_url: str):
        self.session = session
        self._appstream_url = appstream_url

    async def _handle_response(self, resp: aiohttp.ClientResponse):

        match resp.status:
            case 200 | 201:
                return await resp.text(encoding="utf-8")
            case _:
                return None

    async def get(self, branch: str) -> str | None:
        url = self._appstream_url.format(branch=branch)
        async with self.session.get(url) as resp:
            return await self._handle_response(resp)


class DataInfo:
    def __init__(self, client: AppStreamClient, appstream_dir: str):
        self.client = client
        self.dir = appstream_dir
        self._on_update = None
        os.makedirs(self.dir, exist_ok=True)

    async def load_by_branch(self, branch: str, version: str):
        text = await self.client.get(branch)
        if not text:
            return

        old_file = self.get_file_path(branch)
        if old_file:
            os.remove(old_file)

        filename = f"{branch}-{version}.xml"
        filepath = os.path.join(self.dir, filename)
        with open(filepath, "w", encoding="utf-8") as f:
            f.write(text)

        if self._on_update:
            self._on_update(branch)

    def get_file_path(self, branch: str) -> str | None:
        for fname in os.listdir(self.dir):
            if fname.startswith(f"{branch}-") and fname.endswith(".xml"):
                return os.path.join(self.dir, fname)
        return None

    def get_current_version(self, branch: str) -> str | None:
        path = self.get_file_path(branch)
        if path:
            fname = os.path.basename(path)
            return fname[len(branch) + 1 : -4]
        return None

    def has_file(self, branch: str) -> bool:
        return self.get_file_path(branch) is not None


class PackageInfo:
    def __init__(self, client: AppStreamClient, data: DataInfo):
        self.client = client
        self._data = data
        self._cache: dict[str, dict[str, str]] = {}

    def rebuild_cache(self, branch: str) -> None:
        path = self._data.get_file_path(branch)
        if not path:
            self._cache.pop(branch, None)
            return

        result: dict[str, str] = {}
        context = etree.iterparse(path, events=("end",), recover=True)
        for _, elem in context:
            if elem.tag == "component" and elem.get("type") != "addon":
                pkgname = elem.findtext("pkgname")
                app_id = elem.findtext("id")
                if pkgname and app_id:
                    result[pkgname] = app_id
                elem.clear()
        self._cache[branch] = result

    def id_by_pkgname(self, pkgname: str, branch: str) -> str | None:
        if branch not in self._cache:
            return None

        return self._cache[branch].get(pkgname)


class ALTRepoAppStream:

    def __init__(self, session: aiohttp.ClientSession, config: "ALTRepoConfig"):
        self.branches = config.appstream_branches
        self._client = AppStreamClient(session, config.appstream_url)
        self.data = DataInfo(self._client, config.appstream_dir)
        self.package = PackageInfo(self._client, self.data)
        self.data._on_update = self.package.rebuild_cache

        for branch in self.branches:
            if self.data.has_file(branch):
                self.package.rebuild_cache(branch)
