# This file is part of hatch-tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

import re
from collections import namedtuple
from configparser import ConfigParser
from email.utils import parseaddr
from pathlib import Path
from typing import Generator, Optional

from hatchling.metadata.plugin.interface import MetadataHookInterface

TrytonConfig = namedtuple(
    'TrytonConfig', ['version', 'depends', 'extras_depend'])
Author = namedtuple('Author', ['name', 'email'])
_COPYRIGHT_PREFIX = "Copyright (C) "


def _get_tryton_cfg(
        root: str, config: Optional[str] = None) -> Optional[TrytonConfig]:
    tryton_path = Path(root) / (config or 'tryton.cfg')
    if tryton_path.exists():
        config = ConfigParser()
        with tryton_path.open() as f:
            config.read_file(f)
        return TrytonConfig(
            version=config.get('tryton', 'version'),
            depends=config.get(
                'tryton', 'depends', fallback='').strip().splitlines(),
            extras_depend=config.get(
                'tryton', 'extras_depend', fallback='').strip().splitlines(),
            )


def _get_series(version: str) -> tuple[int, int]:
    return tuple(map(int, version.split('.', 2)[:2]))


def _set_version(name: str, series: tuple[int, int]) -> str:
    major, minor = series
    return f'{name} >= {major}.{minor}, < {major}.{minor + 1}'


def _package_name(module: str, prefixes: dict[str, str]) -> str:
    if module in {'ir', 'res', 'tests'}:
        return
    prefix = prefixes.get(module, 'trytond')
    return f'{prefix}_{module}'


def _get_readme(root: str, path: str) -> dict[str, str]:
    readme_path = Path(root) / path
    text = readme_path.read_text()
    if readme_path.suffix == '.rst':
        text = re.sub(
            r'(?m)^\.\. toctree::\r?\n((^$|^\s.*$)\r?\n)*', '', text)
        content_type = 'text/x-rst'
    elif readme_path.suffix == '.md':
        content_type = 'text/markdown'
    else:
        raise ValueError(f"unsupported readme suffix {readme_path.suffix!r}")
    return {
        'content-type': content_type,
        'text': text,
        }


def _get_authors(root: str, path: str) -> Generator[Author, None, None]:
    copyright_path = Path(root) / path
    with copyright_path.open() as f:
        for line in f:
            line = line.strip()
            if not line.lower().startswith(_COPYRIGHT_PREFIX.lower()):
                continue
            try:
                _, name = line[len(_COPYRIGHT_PREFIX):].split(' ', 1)
            except ValueError:
                continue
            if '@' in name:
                name, email = parseaddr(name)
            else:
                email = ''
            yield Author(name=name, email=email)


class TrytonMetadataHook(MetadataHookInterface):
    PLUGIN_NAME = 'tryton'

    def update(self, metadata: dict) -> None:
        tryton_cfg = _get_tryton_cfg(self.root, self.config.get('config'))
        prefixes = self.config.get('prefixes', {})

        dependencies = self.config.get('dependencies', [])

        if tryton_cfg:
            series = _get_series(tryton_cfg.version)
        elif metadata.get('version'):
            series = _get_series(metadata['version'])
        else:
            series = (1, 0)

        if tryton_dependencies := self.config.get('tryton_dependencies', []):
            for package in tryton_dependencies:
                dependencies.append(_set_version(package, series))

        if tryton_cfg:
            if 'version' in metadata.get('dynamic', []):
                metadata['version'] = tryton_cfg.version

            dependencies.append(_set_version('trytond', series))
            for module in tryton_cfg.depends:
                if package := _package_name(module, prefixes):
                    dependencies.append(_set_version(package, series))

        if dependencies:
            if ('dependencies' in metadata
                    or 'dependencies' not in metadata.get('dynamic', [])):
                raise ValueError("'dependencies' must be dynamic")
            metadata['dependencies'] = dependencies

        optional_dependencies = self.config.get('optional-dependencies', {})

        if tryton_cfg and tryton_cfg.extras_depend:
            test_depends = optional_dependencies.setdefault('test', [])
            for module in tryton_cfg.extras_depend:
                if package := _package_name(module, prefixes):
                    test_depends.append(_set_version(package, series))

        if tryton_optional_dependencies := self.config.get(
                'tryton-optional-dependencies', {}):
            for option, packages in tryton_optional_dependencies.items():
                o_dep = optional_dependencies.setdefault(option, [])
                for package in packages:
                    o_dep.append(_set_version(package, series))

        if optional_dependencies:
            if ('optional-dependencies' in metadata
                    or 'optional-dependencies' not in metadata.get(
                        'dynamic', [])):
                raise ValueError("'optional-dependencies' must be dynamic")
            metadata['optional-dependencies'] = optional_dependencies

        if readme := self.config.get('readme'):
            if ('readme' in metadata
                    or 'readme' not in metadata.get('dynamic', [])):
                raise ValueError("'readme' must be dynamic")
            metadata['readme'] = _get_readme(self.root, readme)

        if ((copyright := self.config.get('copyright'))
                and 'authors' in metadata.get('dynamic', [])):
            metadata['authors'] = list(
                map(Author._asdict, _get_authors(self.root, copyright)))
