"""Traitlets-based models for enpkg-related metadata."""
import json
import os

from ..bundled.traitlets import (
    HasTraits, Bool, Enum, Float, Instance, List, Long, Unicode
)
from ..file_formats import EggMetadata, egg_name
from ..file_formats._egg_info import Requirement
from ..file_formats.setuptools_egg import parse_filename
from ..utils import compute_md5
from ..utils.traitlets import NoneOrUnicode


_AVAILABLE_DEFAULT = False
_PRODUCT_DEFAULT = "commercial"


class EnpkgS3IndexEntry(HasTraits):
    """
    Model an S3 legacy index entry.

    Note
    ----
    S3 legacy indexes are the ones generated by our s3 scripts inside
    buildsystem/buildware/scripts/s3, NOT the ones generated by epd_repo.
    """
    available = Bool(_AVAILABLE_DEFAULT)
    build = Long()
    md5 = Unicode()
    mtime = Float()
    egg_basename = Unicode()
    packages = List(Instance(Requirement))
    product = Enum(["pypi", "commercial", "free"], _PRODUCT_DEFAULT)
    python = NoneOrUnicode()
    size = Long()
    type = Enum(["egg"], "egg")
    version = Unicode()

    @classmethod
    def from_data(cls, data):
        """Create a new EnpkgS3IndexEntry instance from a raw dict.

        A raw dict may be a decoded entry from our legacy enpkg S3 index.json

        Note: the passed in dictionary may be modified.
        """
        data["packages"] = [
            Requirement.from_spec_string(s) for s in data.get("packages", [])
        ]
        return cls(**data)

    @classmethod
    def from_egg(cls, path, product=_PRODUCT_DEFAULT,
                 available=_AVAILABLE_DEFAULT):
        kw = {}

        metadata = EggMetadata.from_egg(path)
        kw["version"] = metadata.upstream_version
        kw["build"] = metadata.build
        kw["python"] = metadata._python
        kw["type"] = "egg"
        kw["packages"] = list(metadata.runtime_dependencies)
        kw["product"] = product
        kw["egg_basename"] = metadata.egg_basename

        st = os.stat(path)
        kw["mtime"] = st.st_mtime
        kw["size"] = st.st_size

        kw["available"] = available

        # XXX: keep the hash computing *outside* any other file operation.
        # Opening the same file can cause some IO errors, even on Linux (seen
        # when eggs were on a Samba share).
        kw["md5"] = compute_md5(path)
        return cls(**kw)

    @classmethod
    def from_setuptools_egg(cls, path, build=1, product=_PRODUCT_DEFAULT,
                            available=_AVAILABLE_DEFAULT):
        name, version, pyver, platform = parse_filename(path)
        kw = {"available": True, "build": build, "python": pyver,
              "type": "egg", "version": version}
        st = os.stat(path)
        kw["mtime"] = st.st_mtime
        kw["size"] = st.st_size
        kw["product"] = "free"
        kw["md5"] = compute_md5(path)
        kw["packages"] = []
        kw["egg_basename"] = name

        return cls.from_data(kw)

    @property
    def name(self):
        return self.egg_basename.lower()

    @property
    def s3index_key(self):
        """
        Returns the key in the index.json on S3.
        """
        return egg_name(self.egg_basename, self.version, self.build)

    @property
    def s3index_data(self):
        """
        Returns the data in the index.json on S3.
        """
        data = {
            "available": self.available,
            "build": self.build,
            "md5": self.md5,
            "mtime": self.mtime,
            "name": self.name,
            "packages": [str(p) for p in self.packages],
            "product": self.product,
            "python": self.python,
            "size": self.size,
            "type": self.type,
            "version": self.version,
        }
        return data

    def to_dict(self):
        data = {"available": self.available,
                "build": self.build,
                "egg_basename": self.egg_basename,
                "md5": self.md5,
                "mtime": self.mtime,
                "name": self.name,
                "packages": [str(p) for p in self.packages],
                "product": self.product,
                "python": self.python,
                "size": self.size,
                "type": self.type,
                "version": self.version}
        return data

    def to_json(self):
        return json.dumps(self.to_dict())
