"""
File Purpose: misc. tools related to registry
"""

import os
import sys
import uuid

from ..defaults import DEFAULTS
from ..errors import InputError
from ..tools import (
    UNSET,
    sql_location_string, sql_engine_from_location,
    _maybe_assert_sql_location_exists,
)


### --------------------- Registry database schema --------------------- ###

REGISTRY_SCHEMA_DIR = os.path.dirname(os.path.abspath(__file__))
REGISTRY_SCHEMA_FILES = {  # None has str value, all others have list values.
    None: os.path.join(REGISTRY_SCHEMA_DIR, 'registry_schema.sql'),
    'sqlite': [],  # <-- no extra files needed for sqlite.
    'postgres': [os.path.join(REGISTRY_SCHEMA_DIR, 'registry_schema_postgres.sql')],
}

def registry_schema_sql(engine=None, *, commit=True):
    '''load the SQL schema for the registry database, from registry_schema.sql.
    Returns a string containing the SQL commands that construct the database structure.
        (e.g. it will have CREATE TABLE commands and some CREATE INDEX commands).

    engine: None or str
        if provided, additionally load engine-specific commands.
        E.g. for 'postgres', combines registry_schema.sql with registry_schema_postgres.sql
    commit: bool
        whether to wrap the result as "BEGIN; ... ; COMMIT;"
    '''
    if engine not in REGISTRY_SCHEMA_FILES:
        raise InputError(f"unrecognized engine '{engine}' for registry schema")
    files = [REGISTRY_SCHEMA_FILES[None]]
    if engine is not None:
        files.extend(REGISTRY_SCHEMA_FILES[engine])
    result = []
    if commit:
        result.append('BEGIN;')
        result.append('\n')  # <-- just for style
    for filepath in files:
        result.append(f'\n\n/* -------- from {filepath!r} -------- */\n\n')
        with open(filepath, 'r', encoding='utf-8') as f:
            result.extend(f.readlines())
    if commit:
        result.append('\n')  # <-- just for style
        result.append('COMMIT;')
    return ''.join(result)


### --------------------- Registry location --------------------- ###

def registry_location_default(_platform=None):
    r'''get default registry location. This is the full path to the local pc_registry.db file,
    used by register() whenever DEFAULTS.REGISTRY_LOCATION=None and registry not provided.

    This is always that path, regardless of the current value of DEFAULTS.REGISTRY_LOCATION.
    
    The path is system-dependent. It will look something like this:
    - On Windows: C:\Users\<User>\AppData\Local\plasmacalcs\pc_registry.db
        (or, replace C:\Users\<User> with os.environ['LOCALAPPDATA'] if defined.)
    - On Mac: /Users/<User>/Library/Application Support/plasmacalcs/pc_registry.db
    - On Linux (or, any unrecognized platform): /Users/<User>/.plasmacalcs/pc_registry.db

    _platform: None or str
        if provided, return result computed as if sys.platform=_platform instead of its actual value.
        Mainly useful for debugging purposes.
        'win32' for windows, 'darwin' for Mac, anything else for Linux/Linux-like.
    '''
    if sys.platform == 'win32':
        base_dir = os.environ.get('LOCALAPPDATA',
            os.path.join(os.path.expanduser('~'), 'AppData', 'Local'))
        result = os.path.join(base_dir, 'plasmacalcs', 'pc_registry.db')
    elif sys.platform == 'darwin':
        base_dir = os.path.join(os.path.expanduser('~'), 'Library', 'Application Support')
        result =  os.path.join(base_dir, 'plasmacalcs', 'pc_registry.db')
    else:
        base_dir = os.path.expanduser('~')
        result = os.path.join(base_dir, '.plasmacalcs', 'pc_registry.db')
    return result

def registry_location(location=UNSET, *, assert_exists=None):
    '''get registry location implied by location input.
    Result is always an abspath or ":memory:".

    location: UNSET, None, or str
        UNSET --> use registry implied by DEFAULTS.REGISTRY_LOCATION
            (if DEFAULTS.REGISTRY_LOCATION=None, use registry_location_default(),
             which is system-dependent, e.g. "~/.plasmacalcs/pc_registry.db" on Linux.)
        None --> in-memory, equivalent to ":memory:". Will not edit any files.
        str --> Online database connection string starts with "postgresql://" or "postgres://".
                All other strings interpreted as filepath, and converted to abspath.
    assert_exists: None, True, or False
        True --> assert that implied location already exists as a file.
        False --> assert that implied location does not yet exist as a file.
        None --> no assertion is made.
        (Here, ':memory:' never "already exists", since it always means a new database.)
    '''
    if location is UNSET:
        result = DEFAULTS.REGISTRY_LOCATION
        if result is None:
            result = registry_location_default()
    else:
        result = sql_location_string(location)
    _maybe_assert_sql_location_exists(result, assert_exists=assert_exists)
    return result

def registry_name_default(location, name=UNSET, uuid_=None):
    '''get default registry name implied by name and location inputs.
    This is the default name that will be set, even if user didn't set one.
        (if name is provided as str, will return that str instead.)
    User can update registry name via 'UPDATE sources SET name = newname WHERE source_id = 0'.

    location: str
        ':memory:', abspath to database file, or online database connection string.
    name: UNSET, None, or str
        UNSET --> use basename of location. E.g. "pc_registry.db" by default.
            (if location in-memory, equivalent to name=None)
        None --> no name; use '_unnamed_<short_uuid>' as name,
            filling <short_uuid> with auto-generated unique hex string for this registry.
        str --> use this for registry name.
    uuid_: None or str
        if provided and need short_uuid, use uuid_[:8] instead of generating a new uuid.
    '''
    result = None
    if name is UNSET:
        engine = sql_engine_from_location(location)
        if engine == 'sqlite':
            result = os.path.basename(location)
        else:
            name = None  # get uuid name, below.
    if result is None:
        if name is None:
            if uuid_ is None: uuid_ = uuid.uuid4().hex
            result = f"_unnamed_{uuid_[:8]}"
        else:
            result = name
    return result
