import argparse
import datetime as dt
import logging
import shutil
import sys
from pathlib import Path
from typing import Optional

from datamodel_code_generator.parser.base import Result

from generator.extractor import K8sOpenAPIExtractor
from generator.parser import K8sOpenAPIParser


def _get_default_output_path() -> Path:
    root = Path(__file__).parent.parent
    return root / "src" / "kubedantic" / "models" / "io"


def _get_default_specs_path() -> Path:
    return Path(__file__).parent / "specs"


def _get_options(args):
    parser = argparse.ArgumentParser(
        description="Generates Python data models from Kubernetes OpenAPI specs.",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )

    parser.add_argument(
        "--log-level",
        "-l",
        default="INFO",
        choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
        help="Logging level.",
    )
    parser.add_argument(
        "--output-path",
        "-o",
        default=_get_default_output_path(),
        help="Output directory where the Python data models will be put at.",
    )
    parser.add_argument(
        "--specs-path",
        "-s",
        default=_get_default_specs_path(),
        help="Output directory where the Kubernetes OpenAPI specs will be put at.",
    )

    return parser.parse_args(args)


def _generate_header(k8s_version: Optional[str]) -> str:
    timestamp = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat()

    header = f"""# generated by datamodel-codegen:
#   timestamp: {timestamp}"""

    if k8s_version:
        header += f"\n#   k8s version: {k8s_version}"

    return header + "\n\n"


def _is_empty_result(result: Result) -> bool:
    return result.body.strip() == "from __future__ import annotations"


def _cleanup_empty_modules(path: Path):
    if path.is_dir():
        subdirs = [subdir for subdir in path.iterdir() if subdir.is_dir()]

        for subdir in subdirs:
            _cleanup_empty_modules(subdir)

        subdirs = [subdir for subdir in path.iterdir() if subdir.is_dir()]
        files = [file for file in path.iterdir() if file.is_file()]

        if not subdirs and (
            not files or all(file.stem == "__init__" for file in files)
        ):
            logging.info("Removing empty directory %s", path)
            shutil.rmtree(path)


def _write_result(
    path: Path,
    result: Result,
    output_path: Path,
    header: Optional[str] = None,
):
    if not path.stem == "__init__" and _is_empty_result(result):
        logging.warning("Skipping empty file %s", path)
        return

    output_file = output_path / path.with_suffix(".py")
    logging.info("Generating %s", output_file)
    output_file.parent.mkdir(parents=True, exist_ok=True)

    with open(output_file, "w") as out_file:
        if header and output_file.stem != "__init__":
            out_file.write(header)

        out_file.write(result.body)


def _generate_models(output_path: Path, specs_path: Path):
    extractor = K8sOpenAPIExtractor(output_path=specs_path)
    parser = K8sOpenAPIParser(source=extractor.extract())

    results: dict[tuple[str, ...], Result] = parser.parse()  # type: ignore

    for name, result in sorted(results.items()):
        header = _generate_header(extractor.k8s_version)
        path = Path(*name[1:])
        _write_result(path, result, output_path, header=header)


def run(args):
    options = _get_options(args)

    log_level = logging.getLevelName(options.log_level)
    logging.basicConfig(level=log_level)

    output_path = Path(options.output_path)
    specs_path = Path(options.specs_path)

    _generate_models(output_path, specs_path)
    _cleanup_empty_modules(output_path)


def main():  # pragma: no cover
    run(sys.argv[1:])


if __name__ == "__main__":  # pragma: no cover
    main()
