#!/usr/bin/env python3
# coding: utf-8

import json
import logging
import multiprocessing
import os
import pathlib
import pprint
import sys
import subprocess
import tempfile

from launchpadlib import errors as lp_errors  # fades
from launchpadlib.credentials import RequestTokenAuthorizationEngine, UnencryptedFileCredentialStore
from launchpadlib.launchpad import Launchpad
import requests  # fades


logger = logging.getLogger("subsurface.check_usns")
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.INFO)

APPLICATION = "subsurface-ci"
LAUNCHPAD = "production"
TEAM = "subsurface"
SOURCE_NAME = "subsurface"
SNAPS = {
    "subsurface": {
        "stable": {"recipe": "subsurface-stable"},
    },
}

STORE_URL = "https://api.snapcraft.io/api/v1/snaps" "/details/{snap}?channel={channel}"
STORE_HEADERS = {"X-Ubuntu-Series": "16", "X-Ubuntu-Architecture": "{arch}"}

CHECK_NOTICES_PATH = "/snap/bin/review-tools.check-notices"
CHECK_NOTICES_ARGS = ["--ignore-pockets", "esm-apps"]


def get_store_snap(processor, snap, channel):
    logger.debug("Checking for snap %s on %s in channel %s", snap, processor, channel)
    data = {
        "snap": snap,
        "channel": channel,
        "arch": processor,
    }
    resp = requests.get(STORE_URL.format(**data), headers={k: v.format(**data) for k, v in STORE_HEADERS.items()})
    logger.debug("Got store response: %s", resp)

    try:
        result = json.loads(resp.content)
    except json.JSONDecodeError:
        logger.error("Could not parse store response: %s", resp.content)
    else:
        return result


def fetch_url(entry):
    dest, uri = entry
    r = requests.get(uri, stream=True)
    logger.debug("Downloading %s to %s…", uri, dest)
    if r.status_code == 200:
        with open(dest, "wb") as f:
            for chunk in r:
                f.write(chunk)
    return dest


def check_snap_notices(store_snaps):
    with tempfile.TemporaryDirectory(dir=pathlib.Path.home()) as dir:
        snaps = multiprocessing.Pool(8).map(
            fetch_url,
            (
                (pathlib.Path(dir) / f"{snap['package_name']}_{snap['revision']}.snap", snap["download_url"])
                for snap in store_snaps
            ),
        )

        try:
            notices = subprocess.check_output([CHECK_NOTICES_PATH] + CHECK_NOTICES_ARGS + snaps, encoding="UTF-8")
            logger.debug("Got check_notices output:\n%s", notices)
        except subprocess.CalledProcessError as e:
            logger.error("Failed to check notices:\n%s", e.output)
            sys.exit(2)
        else:
            notices = json.loads(notices)
            return notices


if __name__ == "__main__":
    check_notices = os.path.isfile(CHECK_NOTICES_PATH) and os.access(CHECK_NOTICES_PATH, os.X_OK)

    if not check_notices:
        raise RuntimeError("`review-tools` not found.")

    try:
        lp = Launchpad.login_with(
            APPLICATION,
            LAUNCHPAD,
            version="devel",
            authorization_engine=RequestTokenAuthorizationEngine(LAUNCHPAD, APPLICATION),
            credential_store=UnencryptedFileCredentialStore(os.path.expanduser(sys.argv[1])),
        )
    except NotImplementedError:
        raise RuntimeError("Invalid credentials.")

    ubuntu = lp.distributions["ubuntu"]
    logger.debug("Got ubuntu: %s", ubuntu)

    team = lp.people[TEAM]
    logger.debug("Got team: %s", team)

    errors = []

    for snap, channels in SNAPS.items():
        for channel, snap_map in channels.items():
            logger.info("Processing channel %s for snap %s…", channel, snap)

            try:
                snap_recipe = lp.snaps.getByName(owner=team, name=snap_map["recipe"])
                logger.debug("Got snap: %s", snap_recipe)
            except lp_errors.NotFound as ex:
                logger.error("Snap not found: %s", snap_map["recipe"])
                errors.append(ex)
                continue

            if len(snap_recipe.pending_builds) > 0:
                logger.info("Skipping %s: snap builds pending…", snap_recipe.web_link)
                continue

            store_snaps = tuple(
                filter(
                    lambda snap: snap.get("result") != "error",
                    (get_store_snap(processor.name, snap, channel) for processor in snap_recipe.processors),
                )
            )

            logger.debug("Got store versions: %s", {snap["architecture"][0]: snap["version"] for snap in store_snaps})

            snap_notices = check_snap_notices(store_snaps)[snap]

            for store_snap in store_snaps:
                if str(store_snap["revision"]) not in snap_notices:
                    logger.error(
                        "Revision %s missing in result, see above for any review-tools errors.", store_snap["revision"]
                    )
                    errors.append(f"Revision {store_snap['revision']} missing in result:\n{store_snap}")

            if any(snap_notices.values()):
                logger.info("Found USNs:\n%s", pprint.pformat(snap_notices))
            else:
                logger.info("Skipping %s: no USNs found", snap)
                continue

            logger.info("Triggering %s…", snap_recipe.description or snap_recipe.name)

            snap_recipe.requestBuilds(
                archive=snap_recipe.auto_build_archive,
                pocket=snap_recipe.auto_build_pocket,
                channels=snap_recipe.auto_build_channels,
            )
            logger.debug("Triggered builds: %s", snap_recipe.web_link)

    for error in errors:
        logger.debug(error)

    if errors:
        sys.exit(1)