import logging
import warnings
from pathlib import Path
from typing import Optional

import typer
from typing_extensions import Annotated

from yamlex.api.joiner import assemble_recursively
from yamlex.api.util import (
    adjust_root_logger,
    get_default_extension_dir_path,
    get_default_extension_source_dir_path,
    is_manually_created,
    write_file,
    read_version_properties,
    parse_version,
)
from yamlex.api.exceptions import (
    WrongExtensionStructureError,
    EmptyAssembledExtensionError,
    OverwritingManuallyCreatedFileError,
    UnintendedIndexFileWarning,
)
from yamlex.cli.common_flags import (
    no_file_header_flag,
    force_flag,
    verbose_flag,
    quiet_flag,
    dry_run_flag,
    remove_comments_flag,
    line_length_option,
)


logger = logging.getLogger(__name__)


def join(
    source: Annotated[
        Optional[Path],
        typer.Option(
            "--source",
            "-s",
            help="Path to the directory where individual source component files are stored.",
            show_default="source or src/source",
            dir_okay=True,
            file_okay=False,
            exists=True,
            readable=True,
        )
    ] = None,
    target: Annotated[
        Optional[Path],
        typer.Option(
            "--target",
            "-t",
            help="Path to the target extension.yaml file that will be assembled from parts.",
            show_default="extension/extension.yaml or src/extension/extension.yaml",
            dir_okay=False,
            file_okay=True,
        )
    ] = None,
    dev: Annotated[
        bool,
        typer.Option(
            "--dev",
            "-d",
            help="Add the 'custom:' prefix and embed version into extension.yaml.",
        ),
    ] = False,
    keep_formating: Annotated[
        bool,
        typer.Option(
            "--keep",
            "-k",
            help="Keep formatting and indentation when embedding non-yaml files.",
        ),
    ] = True,
    version: Annotated[
        Optional[str],
        typer.Option(
            "--version",
            "-v",
            help="Explicitly set the version in the extension.yaml.",
            show_default=False,
        ),
    ] = None,
    sort_paths: Annotated[
        bool,
        typer.Option(
            "--sort-paths",
            help="Sort paths alphabetically when traversing source directory before join.",
        ),
    ] = False,
    line_length: line_length_option = None,
    dry_run: dry_run_flag = False,
    remove_comments: remove_comments_flag = False,
    no_file_header: no_file_header_flag = False,
    force: force_flag = False,
    verbose: verbose_flag = False,
    quiet: quiet_flag = False,
) -> None:
    """
    Join individual components into a single [i]extension.yaml[/i] file.

    Assembles all files from the --source directory in a hierarchical order.
    As if the folder structure of the --source directory represents a YAML
    structure.

    [b]Overwriting existing extension.yaml (--no-file-header and --force)[/b]

    Yamlex tries to be cautious not to accidentally overwrite a manually
    created [i]extension.yaml[/i]. If that file contains the "Generated with
    yamlex" header in it, then yamlex overwrites it without hesitation.
    However, when [i]extension.yaml[/i] does not contain that line, yamlex
    does not overwrite it. You can alter this behaviour using --force flag.
    
    When yamlex generates the [i]extension.yaml[/i] from parts, it adds the
    same comment at the top: # Generated by yamlex
    If you would like to disable this behaviour, use the
    --no-file-header flag.

    [b]Development mode[/b]

    When you add the --dev flag, yamlex will add the "custom:" prefix to the
    name of your extension and will put an explicit version into the final
    [i]extension.yaml[/i].
    """
    adjust_root_logger(verbose, quiet)

    source = source or get_default_extension_source_dir_path()
    logger.debug(f"Source files directory: {source}")

    with warnings.catch_warnings(record=True) as caught_warnings:
        warnings.simplefilter("always")
        extension = assemble_recursively(
            source,
            keep_formatting=keep_formating,
            sort_paths=sort_paths,
            dry_run=dry_run,
            remove_comments=remove_comments,
        )

        index_file_paths: list[str] = []
        for w in caught_warnings:
            if w.category is UnintendedIndexFileWarning:
                index_file_paths.append(w.message.args[0])

        # Explicitly disabled until we bump major version. For now we
        # support index.yaml files as usual.
        # if dry_run and index_file_paths:
        #     print(f"Warning! index.yaml files found.")
        #     for i in index_file_paths:
        #         print(i)
        #     print((
        #         "Newer versions of Yamlex no longer use index files as "
        #         "groupers. If you intend to have a field called 'index', "
        #         "ignore this warning. Otherwise, prefix the index file with "
        #         f"a grouper sign like so: +index.yaml. This warning will be "
        #         "removed in the later versions of Yamlex."
        #     ))

    # An assembled extension cannot be an array.
    if isinstance(extension, list):
        raise WrongExtensionStructureError((
            "Error! Invalid source directory structure. "
            "Assembled extension is an array while object is expected."
        ))

    # An assembled extension cannot be empty
    if not extension:
        raise EmptyAssembledExtensionError(
            "Error! Failed to assemble the extension. The result is empty."
        )

    # Figure out the current version
    yaml_version = extension.get("version")

    if dev:
        name = extension.get("name")
        if isinstance(name, str) and not name.startswith("custom:"):
            name = f"custom:{name}"
            extension["name"] = name
            logger.info(f"Dev mode extension name: {name}")

        # If new version is not explicitly specified
        if not version:
            current_version = parse_version(yaml_version)
            if not current_version:
                current_version = read_version_properties("version.properties")
                logger.info(f"Version found in version.properties: {current_version}")

        # Update version, if new explicit version was specified
        if not version:
            version = current_version

        # Explicitly add version to generated extension.yaml
        if yaml_version != version:
            extension["version"] = version
            logger.info(f"Explicit version in dev mode: {version}")

    target = target or get_default_extension_dir_path() / "extension.yaml"
    logger.debug(f"Target file: {target}")

    # Check if the target file exists and was created manually
    if is_manually_created(target) and not force:
        raise OverwritingManuallyCreatedFileError(
            f"The {target} file was created manually. Use --force to overwrite it."
        )

    # Write to output file
    should_add_file_header = not no_file_header
    write_file(
        target,
        extension,
        add_file_header=should_add_file_header,
        line_length=line_length,
        dry_run=dry_run,
    )
