from __future__ import absolute_import
from __future__ import unicode_literals

import json

import jsonschema

from .__version__ import __version__
from .config import Config
from .exceptions import InvalidProjectFile
from .packages import Dependency
from .packages import Locker
from .packages import Package
from .packages import ProjectPackage
from .repositories import Pool
from .repositories.pypi_repository import PyPiRepository
from .spdx import license_by_id
from .utils._compat import Path
from .utils.toml_file import TomlFile


class Poetry:

    VERSION = __version__

    def __init__(self,
                 file,          # type: Path
                 local_config,  # type: dict
                 package,       # type: Package
                 locker         # type: Locker
                 ):
        self._file = TomlFile(file)
        self._package = package
        self._local_config = local_config
        self._locker = locker
        self._config = Config.create('config.toml')

        # Configure sources
        self._pool = Pool()
        for source in self._local_config.get('source', []):
            self._pool.configure(source)

        # Always put PyPI last to prefere private repositories
        self._pool.add_repository(
            PyPiRepository(
                fallback=self._config.setting('settings.pypi.fallback', True)
            )
        )

    @property
    def file(self):
        return self._file

    @property
    def package(self):  # type: () -> Package
        return self._package

    @property
    def local_config(self):  # type: () -> dict
        return self._local_config

    @property
    def locker(self):  # type: () -> Locker
        return self._locker

    @property
    def pool(self):  # type: () -> Pool
        return self._pool

    @classmethod
    def create(cls, cwd):  # type: () -> Poetry
        candidates = [Path(cwd)]
        candidates.extend(Path(cwd).parents)

        for path in candidates:
            poetry_file = path / 'pyproject.toml'

            if poetry_file.exists():
                break

        else:
            raise RuntimeError(
                'Poetry could not find a pyproject.toml file in {} or its parents'.format(cwd)
            )

        local_config = TomlFile(poetry_file.as_posix()).read(True)
        if 'tool' not in local_config or 'poetry' not in local_config['tool']:
            raise RuntimeError(
                '[tool.poetry] section not found in {}'.format(poetry_file.name)
            )
        local_config = local_config['tool']['poetry']

        # Checking validity
        cls.check(local_config)

        # Load package
        name = local_config['name']
        version = local_config['version']
        package = ProjectPackage(name, version, version)
        package.root_dir = poetry_file.parent

        for author in local_config['authors']:
            package.authors.append(author)

        package.description = local_config.get('description', '')
        package.homepage = local_config.get('homepage')
        package.repository_url = local_config.get('repository')
        package.license = local_config.get('license')
        package.keywords = local_config.get('keywords', [])
        package.classifiers = local_config.get('classifiers', [])

        if 'readme' in local_config:
            package.readme = Path(poetry_file.parent) / local_config['readme']

        if 'platform' in local_config:
            package.platform = local_config['platform']

        if 'dependencies' in local_config:
            for name, constraint in local_config['dependencies'].items():
                if name.lower() == 'python':
                    package.python_versions = constraint
                    continue

                package.add_dependency(name, constraint)

        if 'dev-dependencies' in local_config:
            for name, constraint in local_config['dev-dependencies'].items():
                package.add_dependency(name, constraint, category='dev')

        extras = local_config.get('extras', {})
        for extra_name, requirements in extras.items():
            package.extras[extra_name] = []

            # Checking for dependency
            for req in requirements:
                req = Dependency(req, '*')

                for dep in package.requires:
                    if dep.name == req.name:
                        dep.in_extras.append(extra_name)
                        package.extras[extra_name].append(dep)

                        break

        if 'build' in local_config:
            package.build = local_config['build']

        if 'include' in local_config:
            package.include = local_config['include']

        if 'exclude' in local_config:
            package.exclude = local_config['exclude']

        locker = Locker(poetry_file.with_suffix('.lock'), local_config)

        return cls(poetry_file, local_config, package, locker)

    @classmethod
    def check(cls, config, strict=False):  # type: (dict, bool) -> bool
        """
        Checks the validity of a configuration
        """
        schema = (
            Path(__file__).parent
            / 'json' / 'schemas' / 'poetry-schema.json'
        )

        with schema.open() as f:
            schema = json.loads(f.read())

        try:
            jsonschema.validate(
                config,
                schema
            )
        except jsonschema.ValidationError as e:
            message = e.message
            if e.path:
                message = "[{}] {}".format(
                    '.'.join(e.path),
                    message
                )

            raise InvalidProjectFile(message)

        if strict:
            # If strict, check the file more thoroughly

            # Checking license
            license = config.get('license')
            if license:
                try:
                    license_by_id(license)
                except ValueError:
                    raise InvalidProjectFile('Invalid license')

        return True
