from __future__ import annotations

import os
from pathlib import Path
import shutil
import subprocess
import tempfile
from typing import Iterable

import pytest


ROOT_DIR = Path(__file__).resolve().parents[1]
START_DIR = Path.cwd()
TEST_DH_COMPAT = os.environ.get("DH_COMPAT", "13")


def run_dh_tool(command: Iterable[str], cwd: Path) -> subprocess.CompletedProcess[str]:
    env = os.environ.copy()
    env["DH_COMPAT"] = TEST_DH_COMPAT
    env["DH_INTERNAL_TESTSUITE_SILENT_WARNINGS"] = "1"
    env["DEB_RULES_REQUIRES_ROOT"] = "no"
    env["DH_COLORS"] = "never"
    env["DPKG_COLORS"] = "never"
    env.pop("DEB_BUILD_PROFILES", None)
    env.pop("DEB_BUILD_OPTIONS", None)
    root_str = str(ROOT_DIR)
    if root_str not in env.get("PATH", ""):
        env["PATH"] = f"{root_str}:{env.get('PATH', '')}"
    if (ROOT_DIR / "lib").is_dir():
        perl5lib = env.get("PERL5LIB")
        env["PERL5LIB"] = (
            f"{ROOT_DIR}/lib:{perl5lib}" if perl5lib else str(ROOT_DIR / "lib")
        )
    return subprocess.run(
        list(command),
        cwd=cwd,
        env=env,
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        check=False,
    )


def prepare_test_root() -> Path:
    temp_dir = Path(tempfile.mkdtemp(prefix="dh-doc-privacy-"))
    (temp_dir / "debian").mkdir(exist_ok=True)
    for filename in ("debian/control", "debian/changelog"):
        src = ROOT_DIR / filename
        if src.is_file():
            shutil.copy2(src, temp_dir / filename)
    return temp_dir


def slurp(path: Path) -> str:
    return path.read_text(encoding="utf-8")


def write_file(path: Path, text: str) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(text, encoding="utf-8")


def test_markdown_cleanup() -> None:
    test_root = prepare_test_root()
    readme = test_root / "README.md"
    readme_extra = test_root / "README-extra.md"
    original = """# Sample Project

[![CI](https://github.com/example/project/actions/workflows/ci.yml/badge.svg)](https://github.com/example/project/actions)
[![Docs](//readthedocs.org/projects/sample/badge/?version=latest)](https://sample.readthedocs.io/)

![Local](images/logo.png)
![Remote](https://img.shields.io/badge/test-blue)

<p align="center">
  <img src="https://img.shields.io/badge/center-blue" alt="center">
</p>

<p>
  Remote inline <img src="https://img.shields.io/badge/inline-blue"> text stays.
</p>

<div>
  <img src="images/local.svg">
</div>

Some text.
"""
    expected = """# Sample Project

![Local](images/logo.png)
<p>
  Remote inline  text stays.
</p>

<div>
  <img src="images/local.svg">
</div>

Some text.
"""
    original_extra = """Title
=====

![Local](./docs/local.png)
"""
    write_file(readme, original)
    write_file(readme_extra, original_extra)

    result = run_dh_tool(["dh_doc_privacy"], cwd=test_root)
    assert result.returncode == 0, result.stderr

    assert slurp(readme) == expected
    assert slurp(readme_extra) == original_extra

    result = run_dh_tool(["dh_clean"], cwd=test_root)
    assert result.returncode == 0, result.stderr
    assert slurp(readme) == original
    assert slurp(readme_extra) == original_extra

    readme.unlink()
    readme_extra.unlink()
    shutil.rmtree(test_root / "debian/debhelper", ignore_errors=True)
    shutil.rmtree(test_root / "debian/tmp", ignore_errors=True)


def test_exclude_and_no_act() -> None:
    test_root = prepare_test_root()
    readme = test_root / "README.md"
    excluded = test_root / "README-exclude.md"
    original = """Header

![Remote](https://img.shields.io/badge/test-blue)
"""
    expected = """Header
"""
    original_excluded = """Header

![Remote](https://img.shields.io/badge/skip-blue)
"""
    write_file(readme, original)
    write_file(excluded, original_excluded)

    result = run_dh_tool(["dh_doc_privacy", "-X", "exclude"], cwd=test_root)
    assert result.returncode == 0, result.stderr
    assert slurp(readme) == expected
    assert slurp(excluded) == original_excluded

    result = run_dh_tool(["dh_clean"], cwd=test_root)
    assert result.returncode == 0, result.stderr
    assert slurp(readme) == original
    assert slurp(excluded) == original_excluded

    readme.unlink()
    excluded.unlink()

    no_act = test_root / "README-noact.md"
    original_no_act = """Header

![Remote](https://img.shields.io/badge/noact-blue)
"""
    write_file(no_act, original_no_act)

    result = run_dh_tool(["dh_doc_privacy", "--no-act"], cwd=test_root)
    assert result.returncode == 0, result.stderr
    assert slurp(no_act) == original_no_act

    result = run_dh_tool(["dh_clean"], cwd=test_root)
    assert result.returncode == 0, result.stderr
    assert slurp(no_act) == original_no_act

    no_act.unlink()
    shutil.rmtree(test_root / "debian/debhelper", ignore_errors=True)
    shutil.rmtree(test_root / "debian/tmp", ignore_errors=True)


def test_explicit_file_list() -> None:
    test_root = prepare_test_root()
    readme = test_root / "README.md"
    docs_index = test_root / "docs/source/index.rst"
    sidebar = test_root / "docs/source/_templates/sidebar_intro.html"

    write_file(
        readme, "Header\n\n![Remote](https://img.shields.io/badge/readme-blue)\n"
    )
    write_file(
        docs_index,
        """Docs
====

.. image:: https://readthedocs.org/projects/sample/badge/?version=latest
   :target: https://sample.readthedocs.io/en/latest/?badge=latest
""",
    )
    write_file(
        sidebar,
        """<div class="intro">
  <img src="https://img.shields.io/badge/docs-blue" alt="docs">
  <img src="images/local.png" alt="local">
</div>
""",
    )

    result = run_dh_tool(
        [
            "dh_doc_privacy",
            str(docs_index.relative_to(test_root)),
            str(sidebar.relative_to(test_root)),
        ],
        cwd=test_root,
    )
    assert result.returncode == 0, result.stderr
    assert (
        slurp(readme)
        == "Header\n\n![Remote](https://img.shields.io/badge/readme-blue)\n"
    )
    assert slurp(docs_index) == "Docs\n====\n"
    assert slurp(sidebar) == (
        '<div class="intro">\n  <img src="images/local.png" alt="local">\n</div>\n'
    )

    result = run_dh_tool(["dh_clean"], cwd=test_root)
    assert result.returncode == 0, result.stderr
    assert (
        slurp(readme)
        == "Header\n\n![Remote](https://img.shields.io/badge/readme-blue)\n"
    )
    assert slurp(docs_index) == (
        "Docs\n====\n\n"
        ".. image:: https://readthedocs.org/projects/sample/badge/?version=latest\n"
        "   :target: https://sample.readthedocs.io/en/latest/?badge=latest\n"
    )
    assert slurp(sidebar) == (
        '<div class="intro">\n'
        '  <img src="https://img.shields.io/badge/docs-blue" alt="docs">\n'
        '  <img src="images/local.png" alt="local">\n'
        "</div>\n"
    )

    readme.unlink()
    docs_index.unlink()
    sidebar.unlink()
    shutil.rmtree(test_root / "docs", ignore_errors=True)
    shutil.rmtree(test_root / "debian/debhelper", ignore_errors=True)
    shutil.rmtree(test_root / "debian/tmp", ignore_errors=True)


def test_restore_on_clean_normalizes_explicit_paths() -> None:
    test_root = prepare_test_root()
    readme = test_root / "README.md"
    docs_index = test_root / "docs/source/index.rst"

    readme_original = "Header\n\n![Remote](https://img.shields.io/badge/readme-blue)\n"
    docs_original = (
        "Docs\n====\n\n"
        ".. image:: https://readthedocs.org/projects/sample/badge/?version=latest\n"
        "   :target: https://sample.readthedocs.io/en/latest/?badge=latest\n"
    )
    write_file(readme, readme_original)
    write_file(docs_index, docs_original)

    result = run_dh_tool(
        ["dh_doc_privacy", "./README.md", "./docs//source/index.rst"],
        cwd=test_root,
    )
    assert result.returncode == 0, result.stderr
    assert slurp(readme) == "Header\n"
    assert slurp(docs_index) == "Docs\n====\n"

    bucket_index = test_root / "debian/.debhelper/bucket/index"
    assert bucket_index.exists()
    index_lines = bucket_index.read_text(encoding="utf-8").splitlines()
    assert [line.split(" ", 1)[1] for line in index_lines] == [
        "README.md",
        "docs/source/index.rst",
    ]

    result = run_dh_tool(["dh_clean"], cwd=test_root)
    assert result.returncode == 0, result.stderr
    assert slurp(readme) == readme_original
    assert slurp(docs_index) == docs_original

    readme.unlink()
    docs_index.unlink()
    shutil.rmtree(test_root / "docs", ignore_errors=True)
    shutil.rmtree(test_root / "debian/debhelper", ignore_errors=True)
    shutil.rmtree(test_root / "debian/tmp", ignore_errors=True)


def test_restore_on_clean_deduplicates_identical_backups() -> None:
    test_root = prepare_test_root()
    readme = test_root / "README.md"
    guide = test_root / "docs/guide.md"
    original = "Header\n\n![Remote](https://img.shields.io/badge/shared-blue)\n"

    write_file(readme, original)
    write_file(guide, original)

    result = run_dh_tool(["dh_doc_privacy", "README.md", "docs"], cwd=test_root)
    assert result.returncode == 0, result.stderr
    assert slurp(readme) == "Header\n"
    assert slurp(guide) == "Header\n"

    bucket_index = test_root / "debian/.debhelper/bucket/index"
    bucket_files = test_root / "debian/.debhelper/bucket/files"
    index_lines = bucket_index.read_text(encoding="utf-8").splitlines()
    assert len(index_lines) == 2
    assert [line.split(" ", 1)[1] for line in index_lines] == [
        "README.md",
        "docs/guide.md",
    ]
    assert len(list(bucket_files.iterdir())) == 1

    readme.write_text("locally modified\n", encoding="utf-8")
    guide.unlink()

    result = run_dh_tool(["dh_clean"], cwd=test_root)
    assert result.returncode == 0, result.stderr
    assert slurp(readme) == original
    assert slurp(guide) == original

    readme.unlink()
    guide.unlink()
    shutil.rmtree(test_root / "docs", ignore_errors=True)
    shutil.rmtree(test_root / "debian/debhelper", ignore_errors=True)
    shutil.rmtree(test_root / "debian/tmp", ignore_errors=True)


def test_rst_cleanup() -> None:
    test_root = prepare_test_root()
    readme = test_root / "README.rst"
    original = """Sample Project
==============

.. |build| image:: https://github.com/example/project/actions/workflows/ci.yml/badge.svg
   :target: https://github.com/example/project/actions
.. |docs| image:: https://readthedocs.org/projects/sample/badge/?version=latest
   :target: https://sample.readthedocs.io/en/latest/?badge=latest
.. |local| image:: images/local.svg

|build| |docs| |local|

.. image:: https://img.shields.io/badge/remote-blue
   :target: https://example.invalid

.. image:: images/ok.png
   :target: https://example.invalid

Inline <img src="https://img.shields.io/badge/html-blue"> keep text.

Lorem ipsum dolor sit amet.
"""
    expected = """Sample Project
==============

.. |local| image:: images/local.svg

  |local|

.. image:: images/ok.png
   :target: https://example.invalid

Inline  keep text.

Lorem ipsum dolor sit amet.
"""
    write_file(readme, original)

    result = run_dh_tool(["dh_doc_privacy"], cwd=test_root)
    assert result.returncode == 0, result.stderr
    assert slurp(readme) == expected

    result = run_dh_tool(["dh_clean"], cwd=test_root)
    assert result.returncode == 0, result.stderr
    assert slurp(readme) == original

    readme.unlink()
    shutil.rmtree(test_root / "debian/debhelper", ignore_errors=True)
    shutil.rmtree(test_root / "debian/tmp", ignore_errors=True)


def test_rst_inline_image_removal() -> None:
    test_root = prepare_test_root()
    readme = test_root / "README.rst"
    original = """Title
=====

.. image:: https://readthedocs.org/projects/sample/badge/?version=latest
   :target: https://sample.readthedocs.io/en/latest/?badge=latest

.. image:: images/local.png
   :target: https://example.invalid

"""
    expected = """Title
=====

.. image:: images/local.png
   :target: https://example.invalid

"""
    write_file(readme, original)

    result = run_dh_tool(["dh_doc_privacy"], cwd=test_root)
    assert result.returncode == 0, result.stderr
    assert slurp(readme) == expected

    result = run_dh_tool(["dh_clean"], cwd=test_root)
    assert result.returncode == 0, result.stderr
    assert slurp(readme) == original

    readme.unlink()
    shutil.rmtree(test_root / "debian/debhelper", ignore_errors=True)
    shutil.rmtree(test_root / "debian/tmp", ignore_errors=True)


def test_rst_inline_figure_removal() -> None:
    test_root = prepare_test_root()
    readme = test_root / "README.rst"
    original = """bleak
=====

.. figure:: https://raw.githubusercontent.com/hbldh/bleak/master/Bleak_logo.png
   :target: https://github.com/hbldh/bleak
   :alt: Bleak Logo
   :width: 50%

.. figure:: images/local.png
   :alt: Local Logo
   :width: 50%

Bleak is an acronym for Bluetooth Low Energy platform Agnostic Klient.
"""
    expected = """bleak
=====

.. figure:: images/local.png
   :alt: Local Logo
   :width: 50%

Bleak is an acronym for Bluetooth Low Energy platform Agnostic Klient.
"""
    write_file(readme, original)

    result = run_dh_tool(["dh_doc_privacy"], cwd=test_root)
    assert result.returncode == 0, result.stderr
    assert slurp(readme) == expected

    result = run_dh_tool(["dh_clean"], cwd=test_root)
    assert result.returncode == 0, result.stderr
    assert slurp(readme) == original

    readme.unlink()
    shutil.rmtree(test_root / "debian/debhelper", ignore_errors=True)
    shutil.rmtree(test_root / "debian/tmp", ignore_errors=True)


@pytest.fixture(autouse=True)
def cleandir(tmp_path: Path) -> Iterable[None]:
    os.chdir(tmp_path)
    yield
    os.chdir(START_DIR)
