# noqa: INP001
# ruff: noqa: S101; flake8: DC102
"""Unit and fuzz tests for ``ozi-fix`` utility script"""
# Part of ozi.
# See LICENSE.txt in the project root for details.
from __future__ import annotations

import argparse
import difflib
import os
import pathlib
from copy import deepcopy

import pytest
from ozi_spec import METADATA  # pyright: ignore
from ozi_templates import load_environment  # pyright: ignore

import ozi_core.fix.missing  # pyright: ignore
import ozi_core.fix.rewrite_command  # pyright: ignore
import ozi_core.render  # pyright: ignore
from ozi_core.fix.build_definition import unrollable_subdirs  # pyright: ignore

required_pkg_info_patterns = (
    'Author',
    'Author-email',
    'Description-Content-Type',
    'Home-page',
    'License',
    'License-Expression',
    'License-File',
    'Metadata-Version',
    'Name',
    'Programming Language :: Python',
    'Summary',
    'Version',
)

SAMPLE_MESON_BUILD = """# ozi/meson.build
# Part of the OZI Project, under the Apache License v2.0 with LLVM Exceptions.
# See LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
custom_target(
    '_locales.py',
    input: 'generate_locales.py',
    output: '_locales.py',
    command: [python, '@INPUT@'],
    build_by_default: true,
    install: true,
    install_dir: python.get_install_dir() / 'ozi_core',
)
source_files = [
  '__init__.py',
  '__init__.pyi',
  '_i18n.py',
  '_i18n.pyi',
  '_locales.pyi',
  'actions.py',
  'actions.pyi',
  'comment.py',
  'comment.pyi',
  'generate_locales.py',
  'generate_locales.pyi',
  'meson.py',
  'meson.pyi',
  'pkg_extra.py',
  'pkg_extra.pyi',
  'py.typed',
  'render.py',
  'render.pyi',
  'spdx.py',
  'spdx.pyi',
  'trove.py',
  'trove.pyi'
]
foreach file : files(source_files)
    fs.copyfile(file)
    if not meson.is_subproject() or get_option('install-subprojects').enabled()
        python.install_sources(file, pure: true, subdir: 'ozi_core')
    endif
endforeach
source_children = [
    'data',
    'vendor',
    'fix',
    'new',
    'ui',
]
foreach child: source_children
    if child != 'ozi.phony'
        subdir(child)
    endif
endforeach
if false
    executable('source_files', source_files)
    executable('ext_files', ext_files)
    executable('source_children', source_children)
endif
foo_children = []
foreach child: foo_children
    if child != 'ozi.phony'
        subdir(child)
    endif
endforeach
"""

bad_namespace = argparse.Namespace(
    strict=False,
    verify_email=True,
    name='OZI-phony',
    keywords='foo,bar,baz',
    maintainer=[],
    maintainer_email=[],
    author=['foo'],
    author_email=['noreply@oziproject.dev'],
    summary='A' * 512,
    copyright_head='',
    license_expression='CC0-1.0',
    license_file='LICENSE.txt',
    license='CC0 1.0 Universal (CC0 1.0) Public Domain Dedication',
    license_id='ITWASAFICTION',
    license_exception_id='WEMADEITUP',
    topic=['Utilities'],
    status=['7 - Inactive'],
    environment=['Other Environment'],
    framework=['Pytest'],
    audience=['Other Audience'],
    ci_provider='github',
    project_url=['Home, https://oziproject.dev'],
    long_description_content_type='md',
    fix='',
    add=['ozi.phony'],
    remove=['ozi.phony'],
    dist_requires=[],
    allow_file=[],
    missing=True,
)

env = load_environment(vars(bad_namespace), METADATA.asdict())


@pytest.fixture
def bad_project(tmp_path_factory: pytest.TempPathFactory) -> pathlib.Path:
    """Fixture to wrap the ``ozi-new project`` functionality."""
    fn = tmp_path_factory.mktemp('project_')
    namespace = deepcopy(bad_namespace)
    namespace.target = fn
    preprocessed = ozi_core.new.validate.preprocess_arguments(namespace)
    postprocessed = ozi_core.new.validate.postprocess_arguments(preprocessed)
    ozi_core.render.RenderedContent(
        load_environment(vars(postprocessed), METADATA.asdict()),
        postprocessed.target,
        postprocessed.name,
        postprocessed.ci_provider,
        postprocessed.long_description_content_type,
        True,
    ).render()
    return fn


@pytest.mark.parametrize(
    'key',
    [i for i in METADATA.spec.python.src.required.root if i not in ['PKG-INFO']],
)
def test_report_missing_required_root_file(
    bad_project: pathlib.Path,
    key: str,
) -> None:
    """Check that we warn on missing files."""
    os.remove(bad_project.joinpath(key))
    with pytest.raises(RuntimeWarning):
        ozi_core.fix.missing.report(bad_project)


@pytest.mark.parametrize('key', METADATA.spec.python.src.required.test)
def test_report_missing_required_test_file(bad_project: pathlib.Path, key: str) -> None:
    """Check that we warn on missing files."""
    os.remove(bad_project.joinpath('tests') / key)
    with pytest.raises(RuntimeWarning):
        ozi_core.fix.missing.report(bad_project)


@pytest.mark.parametrize('key', METADATA.spec.python.src.required.source)
def test_report_missing_required_source_file(bad_project: pathlib.Path, key: str) -> None:
    """Check that we warn on missing files."""
    os.remove(bad_project.joinpath('ozi_phony') / key)
    with pytest.raises(RuntimeWarning):
        ozi_core.fix.missing.report(bad_project)


@pytest.mark.parametrize('fix', ['test', 'root', 'source'])
def test_Rewriter_bad_project__iadd__dir_nested_warns(  # noqa: N802, DC102, RUF100
    bad_project: pytest.FixtureRequest,
    fix: str,
) -> None:
    rewriter = ozi_core.fix.rewrite_command.Rewriter(
        target=str(bad_project),
        name='ozi_phony',
        fix=fix,
        env=env,
    )
    with pytest.warns(RuntimeWarning):
        rewriter += ['foo/foo/baz/']


@pytest.mark.parametrize('fix', ['test', 'root', 'source'])
def test_Rewriter_bad_project__iadd__dir(  # noqa: N802, DC102, RUF100
    bad_project: pytest.FixtureRequest,
    fix: str,
) -> None:
    rewriter = ozi_core.fix.rewrite_command.Rewriter(
        target=str(bad_project),
        name='ozi_phony',
        fix=fix,
        env=env,
    )
    rewriter += ['foo/']
    assert len(rewriter.commands) == 1


def test_Rewriter_bad_project__iadd__bad_fix(  # noqa: N802, DC102, RUF100
    bad_project: pytest.FixtureRequest,
) -> None:
    rewriter = ozi_core.fix.rewrite_command.Rewriter(
        target=str(bad_project),
        name='ozi_phony',
        fix='',
        env=env,
    )
    with pytest.warns(RuntimeWarning):
        rewriter += ['foo/']
    assert len(rewriter.commands) == 0


def test_Rewriter_bad_project__isub__bad_fix(  # noqa: N802, DC102, RUF100
    bad_project: pytest.FixtureRequest,
) -> None:
    rewriter = ozi_core.fix.rewrite_command.Rewriter(
        target=str(bad_project),
        name='ozi_phony',
        fix='',
        env=env,
    )
    rewriter -= ['foo.py']
    assert len(rewriter.commands) == 1


@pytest.mark.parametrize('fix', ['test', 'root', 'source'])
def test_Rewriter_bad_project__isub__non_existing_child(  # noqa: N802, DC102, RUF100
    bad_project: pytest.FixtureRequest,
    fix: str,
) -> None:
    rewriter = ozi_core.fix.rewrite_command.Rewriter(
        target=str(bad_project),
        name='ozi_phony',
        fix=fix,
        env=env,
    )
    with pytest.raises(RuntimeWarning):
        rewriter -= ['foo/']
    assert len(rewriter.commands) == 0


@pytest.mark.parametrize('fix', ['test', 'root', 'source'])
def test_Rewriter_bad_project__isub__child(  # noqa: N802, DC102, RUF100
    bad_project: pytest.FixtureRequest,
    fix: str,
) -> None:
    rewriter = ozi_core.fix.rewrite_command.Rewriter(
        target=str(bad_project),
        name='ozi_phony',
        fix=fix,
        env=env,
    )
    if fix == 'root':
        pathlib.Path(str(bad_project), 'foo').mkdir()
    elif fix == 'source':
        pathlib.Path(str(bad_project), 'ozi_phony', 'foo').mkdir()
    elif fix == 'test':
        pathlib.Path(str(bad_project), 'tests', 'foo').mkdir()
    rewriter -= ['foo/']
    assert len(rewriter.commands) == 1


@pytest.mark.parametrize('fix', ['test', 'root', 'source'])
def test_Rewriter_bad_project__isub__python_file(  # noqa: N802, DC102, RUF100
    bad_project: pytest.FixtureRequest,
    fix: str,
) -> None:
    rewriter = ozi_core.fix.rewrite_command.Rewriter(
        target=str(bad_project),
        name='ozi_phony',
        fix=fix,
        env=env,
    )
    if fix == 'root':
        pathlib.Path(str(bad_project), 'foo.py').touch()
    elif fix == 'source':
        pathlib.Path(str(bad_project), 'ozi_phony', 'foo.py').touch()
    elif fix == 'test':
        pathlib.Path(str(bad_project), 'tests', 'foo.py').touch()
    rewriter -= ['foo.py']
    assert len(rewriter.commands) == 1


@pytest.mark.parametrize('fix', ['test', 'root', 'source'])
def test_Rewriter_bad_project__isub__file(  # noqa: N802, DC102, RUF100
    bad_project: pytest.FixtureRequest,
    fix: str,
) -> None:
    rewriter = ozi_core.fix.rewrite_command.Rewriter(
        target=str(bad_project),
        name='ozi_phony',
        fix=fix,
        env=env,
    )
    if fix == 'root':
        pathlib.Path(str(bad_project), 'foo').touch()
    elif fix == 'source':
        pathlib.Path(str(bad_project), 'ozi_phony', 'foo').touch()
    elif fix == 'test':
        pathlib.Path(str(bad_project), 'tests', 'foo').touch()
    rewriter -= ['foo']
    assert len(rewriter.commands) == 1


@pytest.mark.parametrize('fix', ['test', 'root', 'source'])
def test_Rewriter_bad_project__iadd__file(  # noqa: N802, DC102, RUF100
    bad_project: pytest.FixtureRequest,
    fix: str,
) -> None:
    rewriter = ozi_core.fix.rewrite_command.Rewriter(
        target=str(bad_project),
        name='ozi_phony',
        fix=fix,
        env=env,
    )
    rewriter += ['foo.py']
    assert len(rewriter.commands) == 1


@pytest.mark.parametrize('fix', ['test', 'root', 'source'])
def test_Rewriter_bad_project__iadd__file_from_template(  # noqa: N802, DC102, RUF100
    bad_project: pytest.FixtureRequest | pathlib.Path,
    fix: str,
) -> None:
    rewriter = ozi_core.fix.rewrite_command.Rewriter(
        target=str(bad_project),
        name='ozi_phony',
        fix=fix,
        env=env,
    )
    pathlib.Path(bad_project / 'templates').mkdir()  # pyright: ignore
    pathlib.Path(bad_project / 'templates' / 'foo.py').touch()  # pyright: ignore
    pathlib.Path(bad_project / 'templates' / 'source').mkdir()  # pyright: ignore
    pathlib.Path(bad_project / 'templates' / 'source' / 'foo.py').touch()  # pyright: ignore
    pathlib.Path(bad_project / 'templates' / 'test').mkdir()  # pyright: ignore
    pathlib.Path(bad_project / 'templates' / 'test' / 'foo.py').touch()  # pyright: ignore
    rewriter += ['foo.py']
    assert len(rewriter.commands) == 1


@pytest.mark.parametrize('fix', ['test', 'root', 'source'])
def test_Rewriter_bad_project__iadd__non_python_file(  # noqa: N802, DC102, RUF100
    bad_project: pytest.FixtureRequest,
    fix: str,
) -> None:
    rewriter = ozi_core.fix.rewrite_command.Rewriter(
        target=str(bad_project),
        name='ozi_phony',
        fix=fix,
        env=env,
    )
    with pytest.warns(RuntimeWarning):
        rewriter += ['foo']
    assert len(rewriter.commands) == 1


def test_meson_unroll_subdirs() -> None:
    x = unrollable_subdirs.parse_string(SAMPLE_MESON_BUILD)
    y = '\n'.join([i.rstrip('\n') for i in x])
    assert "\nfoo_children = ['ozi.phony']\n" in y
    assert "\nsource_children = ['ozi.phony']\nforeach" in y
    assert "\nsubdir('data')\n" in y
    assert "\nsubdir('vendor')\n" in y
    assert "\nsubdir('fix')\n" in y
    assert "\nsubdir('new')\n" in y
    assert "\nsubdir('ui')\n" in y
    assert y != SAMPLE_MESON_BUILD
    z = '\n'.join([i.rstrip('\n') for i in unrollable_subdirs.parse_string(y)])
    assert y == z
    assert ''.join(difflib.context_diff(SAMPLE_MESON_BUILD, y)) == ''.join(
        difflib.context_diff(SAMPLE_MESON_BUILD, z),
    )
