import bz2
import gzip
import json
import pytz

from datetime import datetime
from jinja2 import Environment, PackageLoader
from itertools import chain
from os.path import join, dirname

from .utils import compute_checksums
from . import __version__

from conda.models.version import normalized_version


index_keys = (
    "repodata.json",
    "repodata.json.bz2",
    "repodata.json.gz",
    "index.html",
    "index.html.gz",
)
channel_keys = ("style.css", "icon.png", "index.html", "index.html.gz")
keys_to_remove = ("fn", "channel", "binstar", "target-triplet")


def _merged_packages(repodata):
    return sorted(
        chain(
            repodata.get("packages", {}).items(),
            repodata.get("packages.conda", {}).items(),
        ),
        key=lambda x: x[0],
    )


def _ts_to_dt(dt):
    if dt > 253402300799:  # 9999-12-31
        dt //= 1000  # convert milliseconds to seconds; see #1988
    return datetime.utcfromtimestamp(dt).replace(tzinfo=pytz.timezone("UTC"))


def _get_jinja2_environment():
    def _hyphenate(text):
        parts = text.split("-", 1)
        if len(parts) == 1:
            return parts[0]
        elif len(parts[0]) < len(parts[1]):
            return parts[0] + "-<br/>" + parts[1]
        else:
            return parts[0] + "<br/>-" + parts[1]

    def _filter_strftime(dt, dt_format):
        if isinstance(dt, (int, float)):
            dt = _ts_to_dt(dt)
        return dt.strftime(dt_format)

    def _filter_add_href(text, link, **kwargs):
        if link:
            kwargs_list = [f'href="{link}"']
            kwargs_list.append(f'alt="{text}"')
            kwargs_list += [f'{k}="{v}"' for k, v in kwargs.items()]
            return "<a {}>{}</a>".format(" ".join(kwargs_list), text)
        else:
            return text

    def _human_bytes(n):
        if n < 1024:
            return "%d B" % n
        k = n / 1024
        if k < 1024:
            return "%d KB" % round(k)
        m = k / 1024
        if m < 1024:
            return "%.1f MB" % m
        g = m / 1024
        return "%.2f GB" % g

    environment = Environment(loader=PackageLoader("anaconda_mirror", "templates"))
    environment.filters["human_bytes"] = _human_bytes
    environment.filters["strftime"] = _filter_strftime
    environment.filters["add_href"] = _filter_add_href
    environment.filters["hyphenate"] = _hyphenate
    environment.trim_blocks = True
    environment.lstrip_blocks = True
    return environment


def _make_channel_index_html(channel, sdict):
    mtime = 0
    subdirs = set()
    packages = {}
    for sub, repodata in sdict.items():
        for fn, prec in _merged_packages(repodata):
            subdirs.add(sub)
            name = prec["name"]
            version = normalized_version(prec["version"])
            timestamp = prec.get("timestamp", 0)
            vrec = packages.get(name)
            if vrec is None:
                packages[name] = {
                    "name": name,
                    "subdirs": set(sub),
                    "version": version,
                    "timestamp": timestamp,
                }
            else:
                vrec["version"] = max(
                    vrec["version"], normalized_version(prec["version"])
                )
                vrec["subdirs"].add(sub)
                vrec["timestamp"] = max(vrec["timestamp"], timestamp)
            mtime = max(mtime, timestamp)
            subdirs.add(sub)
    packages = [packages[k] for k in sorted(packages)]
    subdirs = (["noarch"] if "noarch" in subdirs else []) + sorted(subdirs - {"noarch"})
    environment = _get_jinja2_environment()
    template = environment.get_template("channeldata-index.html.j2")
    rendered_html = template.render(
        channel=channel,
        subdirs=subdirs,
        packages=packages,
        current_time=_ts_to_dt(mtime),
        mirror_version=__version__,
    )
    return rendered_html


def _make_subdir_index_html(channel, subdir, repodata, extra_paths):
    packages = _merged_packages(repodata)
    mtime = max(v.get("timestamp", 0) for k, v in packages) if packages else 0
    for prec in extra_paths.values():
        prec["timestamp"] = mtime
    environment = _get_jinja2_environment()
    template = environment.get_template("subdir-index.html.j2")
    return template.render(
        channel=channel,
        subdir=subdir,
        packages=packages,
        current_time=_ts_to_dt(mtime),
        extra_paths=extra_paths,
        mirror_version=__version__,
    )


def build_channel_indices(cname, repodatas):
    index_dict = {}
    index_html = _make_channel_index_html(cname, repodatas)
    data_b = index_html.encode("utf-8")
    for fn in channel_keys:
        if fn == "index.html":
            t_data = data_b
        elif fn == "index.html.gz":
            t_data = gzip.compress(data_b, mtime=0)
        else:
            fpath = join(dirname(__file__), "templates", fn)
            with open(fpath, "rb") as fp:
                t_data = fp.read()
        irec = compute_checksums(t_data)
        irec["_data"] = t_data
        index_dict[fn] = irec
    return index_dict


def build_subdir_indices(cname, platform, repodata):
    if platform != "noarch":
        if not repodata["packages"] and not repodata["packages.conda"]:
            return {}
    index_dict = {}
    repodata.pop("extras", None)
    repodata.pop("indices", None)
    for pkey in ("packages", "packages.conda"):
        for prec in repodata[pkey].values():
            for key in keys_to_remove:
                prec.pop(key, None)
    data_str = json.dumps(repodata, sort_keys=True, separators=(",", ":"))
    data_b = data_str.encode("ascii")
    for fn in index_keys:
        if not fn.startswith("repodata"):
            continue
        if fn.endswith(".gz"):
            t_data = gzip.compress(data_b, mtime=0)
        elif fn.endswith(".bz2"):
            t_data = bz2.compress(data_b)
        else:
            t_data = data_b
        irec = compute_checksums(t_data)
        irec["_data"] = t_data
        index_dict[fn] = irec
    index_html = _make_subdir_index_html(cname, platform, repodata, index_dict)
    data_b = index_html.encode("utf-8")
    for fn in index_keys:
        if fn.startswith("repodata"):
            continue
        elif not fn.startswith("index"):
            if platform != "noarch":
                continue
            fpath = join(dirname(__file__), "templates", fn)
            with open(fpath, "rb") as fp:
                t_data = fp.read()
        elif fn.endswith(".gz"):
            t_data = gzip.compress(data_b, mtime=0)
        else:
            t_data = data_b
        irec = compute_checksums(t_data)
        irec["_data"] = t_data
        index_dict[fn] = irec
    return index_dict
