import logging
import shutil
import stat
import tempfile
from pathlib import Path
from typing import Any, Optional, Union

import click
from rpmautospec_core import check_specfile_features

from ..exc import SpecParseFailure
from ..pkg_history import PkgHistoryProcessor
from ..util import handle_expected_exceptions
from ..version import __version__

log = logging.getLogger(__name__)

RPMAUTOSPEC_TEMPLATE = """## START: Set by rpmautospec
## (rpmautospec version {version})
## RPMAUTOSPEC: {used_features}{autorelease_blurb_if_needed}
## END: Set by rpmautospec
"""

AUTORELEASE_TEMPLATE = """
%define autorelease(e:s:pb:n) %{{?-p:0.}}%{{lua:
    release_number = {autorelease_number:d};
    base_release_number = tonumber(rpm.expand("%{{?-b*}}%{{!?-b:1}}"));
    print(release_number + base_release_number - 1);
}}%{{?-e:.%{{-e*}}}}%{{?-s:.%{{-s*}}}}%{{!?-n:%{{?dist}}}}"""  # noqa: E501


def do_process_distgit(
    spec_or_path: Union[Path, str],
    target: Optional[Union[Path, str]] = None,
    *,
    enable_caching: bool = True,
    error_on_unparseable_spec: bool = True,
) -> bool:
    """Process an RPM spec file in a distgit repository.

    :param spec_or_path: the spec file or path of the repository
    :param enable_caching: whether or not spec file feature test results
        should be cached (disable in long-running processes)
    :param error_on_unparseable_spec: Whether or not failure at parsing
        the current spec file should raise an exception.
    :return: whether or not the spec file needed processing
    """
    processor = PkgHistoryProcessor(spec_or_path)

    if target is None:
        target = processor.specfile
    else:
        target = Path(target)

    # Preserve mode of the target spec file if it is overwritten, otherwise use that of the
    # processed spec file. Otherwise it would inherit the 0600 mode of the temporary file.
    if target.exists():
        specfile_mode = stat.S_IMODE(target.stat().st_mode)
    else:
        specfile_mode = stat.S_IMODE(processor.specfile.stat().st_mode)

    features = check_specfile_features(processor.specfile, enable_caching=enable_caching)
    needs_autochangelog = (
        features.changelog_lineno is None
        and features.autochangelog_lineno is None
        or features.has_autochangelog
    )
    processing_necessary = not features.is_processed and (
        features.has_autorelease or needs_autochangelog
    )
    if not processing_necessary:
        return False

    visitors = [processor.release_number_visitor]
    if needs_autochangelog:
        visitors.append(processor.changelog_visitor)
    result = processor.run(visitors=visitors)

    error = result["verflags"].get("error")
    if error and error_on_unparseable_spec:
        error_detail = result["verflags"]["error-detail"]
        raise SpecParseFailure(
            f"Couldn’t parse spec file {processor.specfile.name}", code=error, detail=error_detail
        )

    autorelease_number = result["release-number"]

    with (
        processor.specfile.open("r", encoding="utf-8", errors="surrogateescape") as specfile,
        tempfile.NamedTemporaryFile(
            "w", encoding="utf-8", errors="surrogateescape"
        ) as tmp_specfile,
    ):
        # Process the spec file into a temporary file...
        used_features = []

        if features.has_autorelease:
            autorelease_blurb_if_needed = AUTORELEASE_TEMPLATE.format(
                autorelease_number=autorelease_number
            )
            used_features.append("autorelease")
        else:
            autorelease_blurb_if_needed = ""

        if needs_autochangelog:
            used_features.append("autochangelog")

        # Write %autorelease macro header
        print(
            RPMAUTOSPEC_TEMPLATE.format(
                version=__version__,
                used_features=", ".join(used_features),
                autorelease_blurb_if_needed=autorelease_blurb_if_needed,
            ),
            file=tmp_specfile,
        )

        for lineno, line in enumerate(specfile, start=1):
            if features.changelog_lineno:
                if features.has_autochangelog and lineno > features.changelog_lineno:
                    break

            else:
                if features.has_autochangelog and lineno == features.autochangelog_lineno:
                    print("%changelog\n", file=tmp_specfile, end="")
                    break
            print(line, file=tmp_specfile, end="")

        if not features.has_autochangelog and features.changelog_lineno is None:
            print("\n%changelog\n", file=tmp_specfile, end="")

        if needs_autochangelog:
            print("## START: Generated by rpmautospec\n", file=tmp_specfile, end="")
            print(
                "\n\n".join(entry.format() for entry in result["changelog"]),
                file=tmp_specfile,
            )
            print("## END: Generated by rpmautospec\n", file=tmp_specfile, end="")

        tmp_specfile.flush()

        # ...and copy it back (potentially across device boundaries)
        shutil.copy2(tmp_specfile.name, target)
        target.chmod(specfile_mode)


@click.command()
@click.argument("spec_or_path", type=click.Path())
@click.argument("target", type=click.Path())
@click.pass_obj
@handle_expected_exceptions
def process_distgit(obj: dict[str, Any], spec_or_path: Path, target: Path) -> None:
    """Work repository history and commit logs into a spec file"""
    do_process_distgit(
        spec_or_path, target, error_on_unparseable_spec=obj["error_on_unparseable_spec"]
    )
