from __future__ import annotations

from dataclasses import dataclass
import importlib
import importlib.util


class DatabaseVersionException(Exception):
    def __init__(self, errorMessage):
        super().__init__(errorMessage)


@dataclass(frozen=True)
class MigrationStep:
    versionFrom: int
    versionTo: int
    moduleName: str
    modulePresent: bool


def getMigrationStepModuleName(versionFrom: int, versionTo: int) -> str:
    return f"ffx.model.migration.step_{int(versionFrom)}_{int(versionTo)}"


def migrationStepModuleExists(versionFrom: int, versionTo: int) -> bool:
    moduleName = getMigrationStepModuleName(versionFrom, versionTo)

    try:
        return importlib.util.find_spec(moduleName) is not None
    except ModuleNotFoundError:
        return False


def getMigrationPlan(currentVersion: int, targetVersion: int) -> list[MigrationStep]:
    version = int(currentVersion)
    target = int(targetVersion)
    migrationPlan = []

    while version < target:
        nextVersion = version + 1
        migrationPlan.append(
            MigrationStep(
                versionFrom=version,
                versionTo=nextVersion,
                moduleName=getMigrationStepModuleName(version, nextVersion),
                modulePresent=migrationStepModuleExists(version, nextVersion),
            )
        )
        version = nextVersion

    return migrationPlan


def loadMigrationStep(versionFrom: int, versionTo: int):
    moduleName = getMigrationStepModuleName(versionFrom, versionTo)

    try:
        module = importlib.import_module(moduleName)
    except ModuleNotFoundError as ex:
        if ex.name == moduleName:
            raise DatabaseVersionException(
                f"No migration path from database version {versionFrom} to {versionTo}"
            ) from ex
        raise

    migrationStep = getattr(module, "applyMigration", None)
    if migrationStep is None:
        raise DatabaseVersionException(
            f"Migration module {moduleName} does not define applyMigration()"
        )

    return migrationStep


def migrateDatabase(databaseContext, currentVersion: int, targetVersion: int, setDatabaseVersion):
    for migrationStepInfo in getMigrationPlan(currentVersion, targetVersion):
        migrationStep = loadMigrationStep(
            migrationStepInfo.versionFrom,
            migrationStepInfo.versionTo,
        )
        migrationStep(databaseContext)
        setDatabaseVersion(databaseContext, migrationStepInfo.versionTo)
