"""Generate and import the SA schema from a gnucash sqlite file."""

import importlib.util
import sqlite3
import sys
import textwrap
from datetime import datetime
from pathlib import Path

# folder with code to insert into the schema generated by sqlacodegen
code_templates = Path(__file__).parent

# folder in HOME to store the generated schemas
path_schemas = Path.home() / ".piecash2" / "schemas"
path_schemas.mkdir(exist_ok=True, parents=True)


def import_module_from_path(path: Path):
    """Import a module from a path."""
    module_name = path.stem
    spec = importlib.util.spec_from_file_location(module_name, path)
    module = importlib.util.module_from_spec(spec)  # type: ignore[arg-type]
    sys.modules[module_name] = module
    spec.loader.exec_module(module)  # type: ignore[union-attr]
    return module


def import_gnucash(book, regenerate_schema=False):
    """Import the gnucash schema from the sqlite database as a module. If the schema does not exist, generate it."""
    modulename = get_schema_name(book)

    if not modulename.exists() or regenerate_schema:
        generate_schema(book, schema_file=modulename)

    return import_module_from_path(modulename)


def generate_schema(db, schema_file: Path):
    """Generate the schema from the sqlite database using sqlacodegen and copy the common files to the db folder."""

    sys.argv = [
        "sqlacodegen_v2",
        "--outfile",
        str(schema_file),
        f"sqlite:///{db.as_posix()}",
        "--option",
        "use_inflect",
    ]
    import sqlacodegen_v2.cli

    sqlacodegen_v2.cli.main()

    # insert before/after the generated schema the common schema
    schema_file_text = (
        textwrap.dedent(
            f"""
        # -*- coding: utf-8 -*-
        '''This file has been generated by piecash2.schema.generation.generate_schema on {datetime.now().isoformat()}.
        '''
        """
        )
        + (code_templates / "sa_schema_pre.py").read_text()
        + schema_file.read_text()
        .replace(
            "from sqlalchemy.ext.declarative import declarative_base as declarative_base_",
            "from sqlalchemy.orm import declarative_base as declarative_base_",
        )
        .replace(
            "from sqlalchemy.orm import Mapped, declarative_base, mapped_column",
            "from sqlalchemy.orm import mapped_column",
        )
        .replace("from sqlalchemy.orm.base import Mapped", "")
        + (code_templates / "sa_schema_post.py").read_text()
        + ""
    )

    try:
        # isort the imports if isort is available
        import isort

        schema_file_text = isort.code(schema_file_text, float_to_top=True)
    except ImportError:
        pass

    try:
        # reformat with black if black is available
        import black

        schema_file_text = black.format_str(schema_file_text, mode=black.FileMode(line_length=140))
    except ImportError:
        pass

    schema_file.write_text(schema_file_text)

    return schema_file_text


def get_version(db: Path):
    """Return the table version of a given book file."""
    with sqlite3.connect(db) as conn:
        c = conn.cursor()
        c.execute("SELECT table_version FROM versions WHERE table_name=='Gnucash'")
        (gnucash_version,) = c.fetchone()

    return gnucash_version


def get_schema_name(book: Path):
    """Return the schema file name for a given book file."""
    v = get_version(book)
    return path_schemas / f"book_schema_sqlite_{v}.py"


def add_book_module_in_path(book):
    module_folder = str(get_schema_name(book).parent)
    if module_folder not in sys.path:
        sys.path.append(module_folder)


def unload_module(mod):
    if mod in sys.modules:
        del sys.modules[mod]


def remove_book_module_in_path(book):
    module_path = get_schema_name(book)
    module_folder = str(module_path.parent)
    module_name = module_path.stem

    unload_module(module_name)

    if module_folder in sys.path:
        sys.path.remove(module_folder)
