from __future__ import annotations

import re
from collections.abc import Sequence
from pathlib import Path
from typing import cast

from wizard.tweaks import WizardINISetting, WizardINISettingEdit


def make_obscript_ini_tweaks(tweaks: Sequence[WizardINISetting]) -> str:
    lines: list[str] = []

    for tweak in tweaks:
        if not isinstance(tweak, WizardINISettingEdit):
            continue
        line = f"{tweak.section} {tweak.setting} to {tweak.value}"
        if tweak.comment:
            line += f" ; {tweak.comment}"

        lines.append(line)

    return "\n".join(["; Generated by Mod Organizer 2 via Wizard"] + sorted(lines))


def make_standard_ini_tweaks(tweaks: Sequence[WizardINISetting]) -> str:
    # group Tweaks per section
    sections: dict[str, list[WizardINISetting]] = {m.section: [] for m in tweaks}
    for m in tweaks:
        sections[m.section].append(m)

    lines: list[str] = []

    for k in sorted(sections):
        s_tweaks = sorted(sections[k], key=lambda m: m.setting)
        lines.append(f"[{k}]")
        for tw in s_tweaks:
            if isinstance(tw, WizardINISettingEdit):
                line = f"{tw.setting} = {tw.value}"
                if tw.comment:
                    line += f" # {tw.comment}"
            else:
                line = f"# {tw.setting} - disabled"
            lines.append(line)
        lines.append("\n")

    return "\n".join(lines[:-1])


def merge_standard_ini_tweaks(tweaks: Sequence[WizardINISetting], file: Path) -> str:
    import sys

    print(f"Cannot merge INI Tweaks for {file.name}.", file=sys.stderr)
    return make_standard_ini_tweaks(tweaks)


def merge_obscript_ini_tweaks(tweaks: Sequence[WizardINISetting], file: Path) -> str:
    # this is from Wrye Bash (and the function is inspired from Wrye Bash)
    reComment = re.compile(";.*", re.U)
    reDeleted = re.compile("" r";-(\w.*?)$", re.U)
    reSet = re.compile("" r"\s*set\s+(.+?)\s+to\s+(.*)", re.I | re.U)
    reSetGS = re.compile("" r"\s*setGS\s+(.+?)\s+(.*)", re.I | re.U)
    reSetNGS = re.compile("" r"\s*SetNumericGameSetting\s+(.+?)\s+(.*)", re.I | re.U)

    _regex_tuples = (
        (reSet, "set", "set {} to {}"),
        (reSetGS, "setgs", "setGS {} {}"),
        (reSetNGS, "setnumericgamesetting", "SetNumericGameSetting {} {}"),
    )

    def _parse_obse_line(line: str):
        for regex, sectionKey, format_string in _regex_tuples:
            match = regex.match(line)
            if match:
                return match, sectionKey, format_string
        return None, None, None

    # read the original file
    with open(file, "r") as fp:
        olines = fp.readlines()

    # map setting name to value
    settings: dict[str, dict[str, WizardINISettingEdit]] = {}
    deleted: dict[str, dict[str, WizardINISetting]] = {}
    for tweak in tweaks:
        if isinstance(tweak, WizardINISettingEdit):
            if tweak.section not in settings:
                settings[tweak.section.lower()] = {}
            settings[tweak.section.lower()][tweak.setting] = tweak
        else:
            if tweak.section not in deleted:
                deleted[tweak.section.lower()] = {}
            deleted[tweak.section.lower()][tweak.setting] = tweak

    # create the lines
    lines: list[str] = []
    for line in olines:
        line = line.rstrip()
        maDeleted = reDeleted.match(line)
        if maDeleted:
            stripped = maDeleted.group(1)
        else:
            stripped = line
        stripped = reComment.sub("", stripped).strip()

        match, section_key, format_string = _parse_obse_line(stripped)

        if match:
            assert format_string is not None
            setting = match.group(1)
            if section_key in settings and setting in settings[section_key]:
                value = settings[section_key][setting].value
                line = format_string.format(setting, value)
                comment = ""
                if settings[section_key][setting].comment:
                    comment = cast(str, settings[section_key][setting].comment)
                    comment += " "
                comment += f"(set by MO2 via Wizard, was {match.group(2)})"
                line = f"{line}  ; {comment}"
                del settings[section_key][setting]
            elif (
                not maDeleted
                and section_key in deleted
                and setting in deleted[section_key]
            ):
                line = f";-{line}"

        lines.append(line)

    for section in settings.values():
        line = ""
        for setting in section.values():
            if setting.section.lower() == "set":
                line = f"{setting.section} {setting.setting} to {setting.value}"
            else:
                line = f"{setting.section} {setting.setting} {setting.value}"

            if setting.comment:
                comment = setting.comment + " (set by MO2 via Wizard)"
            else:
                comment = "(set by MO2 via Wizard)"
            line = f"{line}  ; {comment}"

        lines.append(line)

    return "\n".join(lines)


def make_ini_tweaks(tweaks: Sequence[WizardINISetting]) -> str:
    is_set = [
        tw.section.lower() in ("set", "setgs", "setnumericgamesetting") for tw in tweaks
    ]

    # assume there is no mix
    if all(is_set):
        return make_obscript_ini_tweaks(tweaks)
    else:
        return make_standard_ini_tweaks(tweaks)


def merge_ini_tweaks(tweaks: Sequence[WizardINISetting], file: Path) -> str:
    is_set = [
        tw.section.lower() in ("set", "setgs", "setnumericgamesetting") for tw in tweaks
    ]

    # assume there is no mix
    if all(is_set):
        return merge_obscript_ini_tweaks(tweaks, file)
    else:
        return merge_standard_ini_tweaks(tweaks, file)
