"""Test the sphinx.quickstart module."""

from __future__ import annotations

import time
from io import StringIO
from typing import TYPE_CHECKING

import pytest

from sphinx._cli.util.colour import disable_colour, enable_colour
from sphinx.cmd import quickstart as qs
from sphinx.testing.util import SphinxTestApp

if TYPE_CHECKING:
    from collections.abc import Callable
    from pathlib import Path
    from typing import Any

warnfile = StringIO()


def setup_module():
    disable_colour()


def mock_input(
    answers: dict[str, str], needanswer: bool = False
) -> Callable[[str], str]:
    called = set()

    def input_(prompt: str) -> str:
        if prompt in called:
            raise AssertionError(
                'answer for %r missing and no default present' % prompt
            )
        called.add(prompt)
        for question, answer in answers.items():
            if prompt.startswith(qs.PROMPT_PREFIX + question):
                return answer
        if needanswer:
            raise AssertionError('answer for %r missing' % prompt)
        return ''

    return input_


real_input: Callable[[str], str] = input


def teardown_module():
    qs.term_input = real_input
    enable_colour()


def test_do_prompt() -> None:
    answers = {
        'Q2': 'v2',
        'Q3': 'v3',
        'Q4': 'yes',
        'Q5': 'no',
        'Q6': 'foo',
    }
    qs.term_input = mock_input(answers)  # type: ignore[assignment]

    assert qs.do_prompt('Q1', default='v1') == 'v1'
    assert qs.do_prompt('Q3', default='v3_default') == 'v3'
    assert qs.do_prompt('Q2') == 'v2'
    assert qs.do_prompt('Q4', validator=qs.boolean) is True
    assert qs.do_prompt('Q5', validator=qs.boolean) is False
    with pytest.raises(AssertionError):
        qs.do_prompt('Q6', validator=qs.boolean)


def test_do_prompt_inputstrip() -> None:
    answers = {
        'Q1': 'Y',
        'Q2': ' Yes ',
        'Q3': 'N',
        'Q4': 'N ',
    }
    qs.term_input = mock_input(answers)  # type: ignore[assignment]

    assert qs.do_prompt('Q1') == 'Y'
    assert qs.do_prompt('Q2') == 'Yes'
    assert qs.do_prompt('Q3') == 'N'
    assert qs.do_prompt('Q4') == 'N'


def test_do_prompt_with_nonascii() -> None:
    answers = {
        'Q1': '\u30c9\u30a4\u30c4',
    }
    qs.term_input = mock_input(answers)  # type: ignore[assignment]
    result = qs.do_prompt('Q1', default='\u65e5\u672c')
    assert result == '\u30c9\u30a4\u30c4'


def test_quickstart_defaults(tmp_path):
    answers = {
        'Root path': str(tmp_path),
        'Project name': 'Sphinx Test',
        'Author name': 'Georg Brandl',
        'Project version': '0.1',
    }
    qs.term_input = mock_input(answers)
    d: dict[str, Any] = {}
    qs.ask_user(d)
    qs.generate(d)

    conffile = tmp_path / 'conf.py'
    assert conffile.is_file()
    ns: dict[str, Any] = {}
    exec(conffile.read_text(encoding='utf8'), ns)  # NoQA: S102
    assert ns['extensions'] == []
    assert ns['templates_path'] == ['_templates']
    assert ns['project'] == 'Sphinx Test'
    assert ns['copyright'] == '%s, Georg Brandl' % time.strftime('%Y')
    assert ns['version'] == '0.1'
    assert ns['release'] == '0.1'
    assert ns['html_static_path'] == ['_static']

    assert (tmp_path / '_static').is_dir()
    assert (tmp_path / '_templates').is_dir()
    assert (tmp_path / 'index.rst').is_file()
    assert (tmp_path / 'Makefile').is_file()
    assert (tmp_path / 'make.bat').is_file()


def test_quickstart_all_answers(tmp_path):
    answers = {
        'Root path': str(tmp_path),
        'Separate source and build': 'y',
        'Name prefix for templates': '.',
        'Project name': 'STASI™',
        'Author name': "Wolfgang Schäuble & G'Beckstein",
        'Project version': '2.0',
        'Project release': '2.0.1',
        'Project language': 'de',
        'Source file suffix': '.txt',
        'Name of your master document': 'contents',
        'autodoc': 'y',
        'doctest': 'yes',
        'intersphinx': 'no',
        'todo': 'y',
        'coverage': 'no',
        'imgmath': 'N',
        'mathjax': 'no',
        'ifconfig': 'no',
        'viewcode': 'no',
        'githubpages': 'no',
        'Create Makefile': 'no',
        'Create Windows command file': 'no',
        'Do you want to use the epub builder': 'yes',
    }
    qs.term_input = mock_input(answers, needanswer=True)
    d: dict[str, Any] = {}
    qs.ask_user(d)
    qs.generate(d)

    conffile = tmp_path / 'source' / 'conf.py'
    assert conffile.is_file()
    ns: dict[str, Any] = {}
    exec(conffile.read_text(encoding='utf8'), ns)  # NoQA: S102
    assert ns['extensions'] == [
        'sphinx.ext.autodoc',
        'sphinx.ext.doctest',
        'sphinx.ext.todo',
    ]
    assert ns['templates_path'] == ['.templates']
    assert ns['source_suffix'] == '.txt'
    assert ns['root_doc'] == 'contents'
    assert ns['project'] == 'STASI™'
    assert ns['copyright'] == "%s, Wolfgang Schäuble & G'Beckstein" % time.strftime(
        '%Y'
    )
    assert ns['version'] == '2.0'
    assert ns['release'] == '2.0.1'
    assert ns['todo_include_todos'] is True
    assert ns['html_static_path'] == ['.static']

    assert (tmp_path / 'build').is_dir()
    assert (tmp_path / 'source' / '.static').is_dir()
    assert (tmp_path / 'source' / '.templates').is_dir()
    assert (tmp_path / 'source' / 'contents.txt').is_file()


def test_generated_files_eol(tmp_path):
    answers = {
        'Root path': str(tmp_path),
        'Project name': 'Sphinx Test',
        'Author name': 'Georg Brandl',
        'Project version': '0.1',
    }
    qs.term_input = mock_input(answers)
    d: dict[str, Any] = {}
    qs.ask_user(d)
    qs.generate(d)

    def assert_eol(filename: Path, eol: str) -> None:
        content = filename.read_bytes().decode()
        assert all(l[-len(eol) :] == eol for l in content.splitlines(keepends=True))

    assert_eol(tmp_path / 'make.bat', '\r\n')
    assert_eol(tmp_path / 'Makefile', '\n')


def test_quickstart_and_build(tmp_path):
    answers = {
        'Root path': str(tmp_path),
        'Project name': 'Fullwidth characters: \u30c9\u30a4\u30c4',
        'Author name': 'Georg Brandl',
        'Project version': '0.1',
    }
    qs.term_input = mock_input(answers)
    d: dict[str, Any] = {}
    qs.ask_user(d)
    qs.generate(d)

    app = SphinxTestApp('html', srcdir=tmp_path, warning=warnfile)
    app.build(force_all=True)
    app.cleanup()
    warnings = warnfile.getvalue()
    assert not warnings


def test_default_filename(tmp_path):
    answers = {
        'Root path': str(tmp_path),
        'Project name': '\u30c9\u30a4\u30c4',  # Fullwidth characters only
        'Author name': 'Georg Brandl',
        'Project version': '0.1',
    }
    qs.term_input = mock_input(answers)
    d: dict[str, Any] = {}
    qs.ask_user(d)
    qs.generate(d)

    conffile = tmp_path / 'conf.py'
    assert conffile.is_file()
    ns: dict[str, Any] = {}
    exec(conffile.read_text(encoding='utf8'), ns)  # NoQA: S102


def test_extensions(tmp_path):
    qs.main([
        '-q',
        '-p',
        'project_name',
        '-a',
        'author',
        '--extensions',
        'foo,bar,baz',
        str(tmp_path),
    ])

    conffile = tmp_path / 'conf.py'
    assert conffile.is_file()
    ns: dict[str, Any] = {}
    exec(conffile.read_text(encoding='utf8'), ns)  # NoQA: S102
    assert ns['extensions'] == ['foo', 'bar', 'baz']


def test_exits_when_existing_confpy(monkeypatch):
    # The code detects existing conf.py with path.is_file()
    # so we mock it as True with pytest's monkeypatch
    monkeypatch.setattr('os.path.isfile', lambda path: True)

    qs.term_input = mock_input({
        'Please enter a new root path (or just Enter to exit)': '',
    })
    d: dict[str, Any] = {}
    with pytest.raises(SystemExit):
        qs.ask_user(d)
