import os
import json
import glob
import click

from wurker.settings import conf
from wurker.cli.base import BaseCommand, CommandNotReady
from wurker.models import Base, LocalBase, User, ApiAuth
from wurker.cli.commands.admin.status import StatusCommand
from wurker.db import create_schema, seed_data, seed_control, make_session, update_status, \
    migrate, schema_exists

from wurker.ui import create_app
from wurker.ui.db import init_db

from werkzeug.security import generate_password_hash


class SeedCommand(BaseCommand):
    """Encapsulate seed command."""

    # Init arguments
    only : str
    drop : bool

    def __init__(self, only:str='', drop:bool=False, logger=None):
        BaseCommand.__init__(self, logger=logger)

        # Seeds are special
        self.etc_root = conf.etc_root
        self.seed_root = conf.seed_root
        if not self.seed_root or not os.path.isdir(self.seed_root):
            raise CommandNotReady(f"Seed root not found: '{self.seed_root}'")

        seed_glob = conf.seed_glob
        self.seed_glob = os.path.join(self.seed_root, seed_glob)
        self.seed_paths = sorted(glob.glob(self.seed_glob))

        # Init args
        self.only = only
        self.drop = drop


    def run(self):
        """Seed schema and data for this Wurker instance. Note NO Wurker given!"""

        # Seed Wurker config schema and data?
        if not self.only or 'wurker' in self.only:
            if error := self.seed_wurker():
                return error

        # Seed local Hive schema and data?
        if not self.only or 'local' in self.only:
            if error := self.seed_local():
                return error


    def seed_wurker(self):
        """Seed schema (or drop it)."""

        db_conf = self.remote_db_conf

        # Drop the schema?
        if self.drop:
            click.echo(f"Dropping database '{db_conf.database}'...")
            if schema_exists(db_conf, Base, return_bool=True) and not migrate(db_conf, "downgrade"):
                return f"Unable to drop database '{db_conf.database}'"
            return

        # OK we're seeding, so create schema...
        click.echo(f"Creating schema if it does not exist: '{db_conf.database}'...")

        if not schema_exists(db_conf, Base, return_bool=True) and not migrate(db_conf, "upgrade"):
            return f"Unable to create schema '{db_conf.database}'"

        click.echo(f"  . Schema exists or was created: '{db_conf.database}'")

        # ...then seed control data & seed data...
        error = self._seed_control(db_conf)
        if error:
            return error

        error = self._seed_data(db_conf)
        if error:
            return error

        error = self._seed_status(db_conf)
        if error:
            return error


    def _seed_control(self, db_conf):
        """Seed control for this Wurker instance."""

        click.echo(f"Seeding 'wurker_control'...")

        # Make the control record (TODO: make this distributable)
        if not seed_control(db_conf, self.app_id):
            return f"Unable to seed 'wurker_control' record"

        click.echo(f"  . Seeded 'wurker_control' for app ID ({self.app_id})")


    def _seed_data(self, db_conf):
        """Seed config data."""

        seed_paths = sorted(self.seed_paths)
        click.echo(f"Seeding data on '{db_conf.database}' from seed paths: {seed_paths}...")

        for seed_path in [os.path.abspath(p) for p in seed_paths]:
            if not os.path.exists(seed_path):
                return f"Seed file not found: '{seed_path}'"

            seed, error, warnings = seed_data(db_conf, self.app_id, seed_path, self.etc_root)

            if error:
                return f"Error seeding ({seed}) from '{seed_path}': {error}"
            elif seed == None:
                click.echo(f"  . Already seeded: '{seed_path}'")
            else:
                if warnings:
                    click.echo(f"  . Seeded ({seed}): '{seed_path}' with warnings:")
                    for s, warning in warnings:
                        click.echo(f"    ! WARNING: Seed ({s}): {warning}")
                else:
                    click.echo(f"  . Seeded: '{seed_path}'")

        click.echo("  . All data seeded")


    def _seed_status(self, db_conf):
        """Seed wurker_status from local settings and derived values."""

        click.echo(f"Seeding 'wurker_status' on: '{db_conf.database}'...")

        status_cmd = StatusCommand()
        status = status_cmd.run(return_result=True)

        # This function raises an Exception per missing required key
        try:
            update_status(db_conf, self.app_id, status)
        except Exception as ex:
           return f"Unable to seed 'wurker_status' record on host '{db_conf.database}' due to exception: {ex}"

        click.echo(f"  . Seeded 'wurker_status' for app ID ({self.app_id})")


    def seed_local(self):
        """Seed local DB tables, including hive and UI data."""

        db_conf = self.local_db_conf
        db_file = db_conf.database

        if self.drop:
            click.echo(f"Deleting local database file: '{db_file}'...")
            if os.path.exists(db_file):
                os.remove(db_file)
                click.echo(f"  . Local database file was deleted: '{db_file}'")
            else:
                click.echo(f"  . Local database file does not exist: '{db_file}'")

            return

        # Seed hive and UI tables and data
        self._seed_hive(db_conf, db_file)
        self._seed_ui(db_conf, db_file)


    def _seed_hive(self, db_conf, db_file):
        """Seed local hive DB."""

        click.echo(f"Creating local database file: '{db_file}'...")

        if not os.path.exists(os.path.dirname(db_file)):
            os.makedirs(os.path.dirname(db_file))

        if not create_schema(db_conf, LocalBase):
            click.echo(f"Unable to create local database file: '{db_file}'")
        else:
            click.echo(f"  . Local database file was created: '{db_file}'")


    def _seed_ui(self, db_conf, db_file):
        """Seed Flask App DB."""

        db_conf = self.local_db_conf
        db_file = db_conf.database

        click.echo(f"Creating app database file: '{db_file}'...")
        db_dir = os.path.dirname(db_file)
        if not os.path.exists(db_dir):
            os.makedirs(db_dir)

        # Note we tell Flask app to create its own DB
        try:
            app = create_app()
            with app.app_context():
                init_db()
            click.echo(f"  . App database file was created: '{db_file}'")
        except Exception as ex:
            click.echo(f"Exception encountered during app database file '{db_file}' creation: {ex}")
            raise ex

        # Seed SQLite table data
        session = make_session(db_conf=db_conf)

        click.echo(f"Seeding 'user' in app database file: '{db_file}'...")
        self._seed_auth(db_dir, 'user', User, session)

        click.echo(f"Seeding 'api_auth' in app database file: '{db_file}'...")
        self._seed_auth(db_dir, 'api_auth', ApiAuth, session)


    def _seed_auth(self, db_dir, table, model, session):
        """Seed Wurker auth."""

        # If data/user.json exists, then seed the app.db from it
        users = None
        user_file = os.path.join(db_dir, f"{table}.json")
        if os.path.exists(user_file):
            with open(user_file, 'r') as f:
                users = json.load(f)

        if not users:
            return

        with session() as db:
            for user_cfg in users:
                if "username" not in user_cfg or "password" not in user_cfg:
                    click.echo(f"  . Skipping invalid '{table}.json' data: {user_cfg}")
                    continue

                user_cfg["password"] = generate_password_hash(user_cfg["password"])
                user = model(**user_cfg)
                db.add(user)
                db.commit()

                click.echo(f"  . Created app user ({user.id}): '{user.username}'")
