# -*- coding: utf-8 -*-
# Copyright 2017 ACSONE SA/NV (<http://acsone.eu>)
# License GPL-3.0 or later (http://www.gnu.org/licenses/gpl.html).

import logging
import sys
from configparser import ConfigParser

import click
import pkg_resources
import pylint.lint

from .config import AcsooConfig
from .main import main
from .manifest import get_installable_addons
from .tools import cfg_path, cmd_string, log_cmd


def _get_failures(linter_stats, expected):
    fails = []
    no_fails = []
    for msg, count in linter_stats["by_msg"].items():
        if not count:
            continue
        if msg in expected:
            expected_count = expected[msg]
            if expected_count is None or count == expected_count:
                no_fails.append((msg, count, expected_count))
            else:
                fails.append((msg, count, expected_count))
        else:
            fails.append((msg, count, None))
    return (sorted(fails, key=lambda i: i[0]), sorted(no_fails, key=lambda i: i[0]))


def _failures_to_str(fails, no_fails):
    def _r(results):
        for msg, count, expected_count in results:
            res.append("  {}: {}".format(msg, count))
            if expected_count is not None:
                res.append(" (expected {})".format(expected_count))
            res.append("\n")

    res = []
    if no_fails:
        res.append(click.style("messages that did not cause failure:\n", bold=True))
        _r(no_fails)
    if fails:
        res.append(click.style("messages that caused failure:\n", bold=True))
        _r(fails)
    return "".join(res)


def _parse_msg_string(s):
    res = {}
    s = s or ""
    for msg in s.split(","):
        msg = msg.strip()
        if not msg:
            continue
        if ":" in msg:
            msg, count = msg.split(":", 2)
            msg = msg.strip()
            count = int(count)
            res[msg] = count
        else:
            res[msg] = None
    return res


def _consolidate_expected(rcfile, expected):
    config = ConfigParser()
    config.read([rcfile])
    res = {}
    if config.has_option("ACSOO", "expected"):
        res.update(_parse_msg_string(config.get("ACSOO", "expected")))
    res.update(expected)
    return res


def do_pylintcmd(load_plugins, rcfile, module, expected, pylint_options):
    # import pdb; pdb.set_trace()
    if not module:
        module = []
        addons = get_installable_addons()
        module.extend(addon_dir for addon_name, (addon_dir, manifest) in addons.items())
        if not module:
            raise click.UsageError(
                "Please provide module or package " "to lint (--module)."
            )
    cmd = (
        ["--load-plugins", load_plugins, "--rcfile", rcfile]
        + list(pylint_options)
        + list(module)
    )
    log_cmd(["pylint"] + cmd, level=logging.INFO)
    if pkg_resources.get_distribution(
        "pylint"
    ).parsed_version >= pkg_resources.parse_version("2.0"):
        lint_res = pylint.lint.Run(cmd[:], do_exit=False)  # pylint2
    else:
        lint_res = pylint.lint.Run(cmd[:], exit=False)  # pylint1
    sys.stdout.flush()
    sys.stderr.flush()
    expected = _consolidate_expected(rcfile, expected)
    fails, no_fails = _get_failures(lint_res.linter.stats, expected)
    if fails or no_fails:
        msg = cmd_string(["pylint"] + cmd)
        msg += "\n"
        msg += _failures_to_str(fails, no_fails)
        click.echo("\n")
        click.echo(msg)
    if fails:
        raise click.ClickException("pylint errors detected.")


@click.command()
@click.option(
    "--module",
    "-m",
    metavar="module_or_package",
    multiple=True,
    help="Module or package to lint (default: autodetected "
    "odoo/addons or odoo_addons or in current directory).",
)
@click.option(
    "--load-plugins",
    metavar="PLUGINS",
    default="pylint_odoo",
    help="Pylint plugins to use (default: pylint_odoo).",
)
@click.option(
    "--rcfile",
    type=click.Path(dir_okay=False, exists=True),
    default=cfg_path("pylint.cfg"),
    help="Pylint configuration file. Default is provided by acsoo.",
)
@click.option(
    "--expected",
    "-e",
    "expected",
    metavar="MSG-IDS",
    help="Do not fail on these messages.",
)
@click.argument("pylint-options", nargs=-1)
@click.pass_context
def pylintcmd(ctx, load_plugins, rcfile, module, expected, pylint_options):
    """Run pylint with reasonable defaults.

    You may pass additional
    options to pylint using '--' to separate them from
    acsoo options.

    Default options are read from the
    [pylint] section of the acsoo configuration file.
    The pylint-options key in that section can be used
    to provide default additional pylint options (one
    per line). Example configuration file:

    \b
    [pylint]
    expected=fixme:5
    pylint-options=
      --disable=all
      --enable=fixme

    The above configuration file is equivalent to:

    acsoo pylint --expected=fixme:5 -- --disable=all --enable=fixme
    """
    expected = _parse_msg_string(expected)
    default_pylint_options = (ctx.default_map or {}).get("default_pylint_options", [])
    pylint_options = default_pylint_options + list(pylint_options)
    do_pylintcmd(load_plugins, rcfile, module, expected, pylint_options)


main.add_command(pylintcmd, name="pylint")


def _read_defaults(config):
    section = "pylint"
    defaults = dict(
        module=config.getlist(section, "module"),
        load_plugins=config.get(section, "load-plugins", default="pylint_odoo"),
        rcfile=config.get(section, "rcfile", default=cfg_path("pylint.cfg")),
        expected=config.get(section, "expected", flatten=True),
        default_pylint_options=config.getlist(section, "pylint-options"),
    )
    return dict(pylint=defaults)


AcsooConfig.add_default_map_reader(_read_defaults)
