import logging
from pathlib import Path
from typing import TYPE_CHECKING

from clan_lib.errors import ClanError
from clan_lib.git import commit_files

log = logging.getLogger(__name__)

if TYPE_CHECKING:
    from clan_cli.vars.generator import Generator
    from clan_lib.machines.machines import Machine


def _migration_file_exists(
    machine: "Machine",
    generator: "Generator",
    fact_name: str,
) -> bool:
    for file in generator.files:
        if file.name == fact_name:
            break
    else:
        msg = f"Could not find file {fact_name} in generator {generator.name}"
        raise ClanError(msg)

    is_secret = file.secret
    if is_secret:
        if machine.secret_facts_store.exists(generator.name, fact_name):
            return True
        machine.debug(
            f"Cannot migrate fact {fact_name} for service {generator.name}, as it does not exist in the secret fact store",
        )
    if not is_secret:
        if machine.public_facts_store.exists(generator.name, fact_name):
            return True
        machine.debug(
            f"Cannot migrate fact {fact_name} for service {generator.name}, as it does not exist in the public fact store",
        )
    return False


def _migrate_file(
    machine: "Machine",
    generator: "Generator",
    var_name: str,
    service_name: str,
    fact_name: str,
) -> list[Path]:
    for file in generator.files:
        if file.name == var_name:
            break
    else:
        msg = f"Could not find file {fact_name} in generator {generator.name}"
        raise ClanError(msg)

    paths = []

    if file.secret:
        old_value = machine.secret_facts_store.get(service_name, fact_name)
        paths_list = machine.secret_vars_store.set(
            generator, file, old_value, machine.name, is_migration=True
        )
        paths.extend(paths_list)
    else:
        old_value = machine.public_facts_store.get(service_name, fact_name)
        paths_list = machine.public_vars_store.set(
            generator, file, old_value, machine.name, is_migration=True
        )
        paths.extend(paths_list)

    return paths


def migrate_files(
    machine: "Machine",
    generator: "Generator",
) -> None:
    not_found = []
    files_to_commit = []
    for file in generator.files:
        if _migration_file_exists(machine, generator, file.name):
            if generator.migrate_fact is None:
                msg = f"Generator {generator.name} has no migrate_fact defined"
                raise ClanError(msg)
            files_to_commit += _migrate_file(
                machine,
                generator,
                file.name,
                generator.migrate_fact,
                file.name,
            )
        else:
            not_found.append(file.name)
    if len(not_found) > 0:
        msg = f"Could not migrate the following files for generator {generator.name}, as no fact or secret exists with the same name: {not_found}"
        raise ClanError(msg)
    commit_files(
        files_to_commit,
        machine.flake_dir,
        f"migrated facts to vars for generator {generator.name} for machine {machine.name}",
    )


def check_can_migrate(
    machine: "Machine",
    generator: "Generator",
) -> bool:
    service_name = generator.migrate_fact
    if not service_name:
        return False
    # ensure that none of the generated vars already exist in the store
    all_files_missing = True
    all_files_present = True
    for file in generator.files:
        if file.secret:
            if machine.secret_vars_store.exists(generator, file.name):
                all_files_missing = False
            else:
                all_files_present = False
        elif machine.public_vars_store.exists(generator, file.name):
            all_files_missing = False
        else:
            all_files_present = False

    if not all_files_present and not all_files_missing:
        msg = f"Cannot migrate facts for generator {generator.name} as some files already exist in the store"
        raise ClanError(msg)
    if all_files_present:
        # all files already migrated, no need to run migration again
        return False

    # ensure that all files can be migrated (exists in the corresponding fact store)
    return bool(
        all(
            _migration_file_exists(machine, generator, file.name)
            for file in generator.files
        ),
    )
