"""Script to convert an old-format home-assistant.db to a new format one."""

import argparse
import os.path
import sqlite3
import sys

from datetime import datetime
from typing import Optional, List

import homeassistant.config as config_util
import homeassistant.util.dt as dt_util
# pylint: disable=unused-import
from homeassistant.components.recorder import REQUIREMENTS  # NOQA


def ts_to_dt(timestamp: Optional[float]) -> Optional[datetime]:
    """Turn a datetime into an integer for in the DB."""
    if timestamp is None:
        return None
    return dt_util.utc_from_timestamp(timestamp)


# Based on code at
# http://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console
def print_progress(iteration: int, total: int, prefix: str='', suffix: str='',
                   decimals: int=2, bar_length: int=68) -> None:
    """Print progress bar.

    Call in a loop to create terminal progress bar
    @params:
        iteration   - Required  : current iteration (Int)
        total       - Required  : total iterations (Int)
        prefix      - Optional  : prefix string (Str)
        suffix      - Optional  : suffix string (Str)
        decimals    - Optional  : number of decimals in percent complete (Int)
        barLength   - Optional  : character length of bar (Int)
    """
    filled_length = int(round(bar_length * iteration / float(total)))
    percents = round(100.00 * (iteration / float(total)), decimals)
    line = '#' * filled_length + '-' * (bar_length - filled_length)
    sys.stdout.write('%s [%s] %s%s %s\r' % (prefix, line,
                                            percents, '%', suffix))
    sys.stdout.flush()
    if iteration == total:
        print("\n")


def run(script_args: List) -> int:
    """Run the actual script."""
    # pylint: disable=invalid-name
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker
    from homeassistant.components.recorder import models

    parser = argparse.ArgumentParser(
        description="Migrate legacy DB to SQLAlchemy format.")
    parser.add_argument(
        '-c', '--config',
        metavar='path_to_config_dir',
        default=config_util.get_default_config_dir(),
        help="Directory that contains the Home Assistant configuration")
    parser.add_argument(
        '-a', '--append',
        action='store_true',
        default=False,
        help="Append to existing new format SQLite database")
    parser.add_argument(
        '--uri',
        type=str,
        help="Connect to URI and import (implies --append)"
             "eg: mysql://localhost/homeassistant")
    parser.add_argument(
        '--script',
        choices=['db_migrator'])

    args = parser.parse_args()

    config_dir = os.path.join(os.getcwd(), args.config)  # type: str

    # Test if configuration directory exists
    if not os.path.isdir(config_dir):
        if config_dir != config_util.get_default_config_dir():
            print(('Fatal Error: Specified configuration directory does '
                   'not exist {} ').format(config_dir))
            return 1

    src_db = '{}/home-assistant.db'.format(config_dir)
    dst_db = '{}/home-assistant_v2.db'.format(config_dir)

    if not os.path.exists(src_db):
        print("Fatal Error: Old format database '{}' does not exist".format(
            src_db))
        return 1
    if not args.uri and (os.path.exists(dst_db) and not args.append):
        print("Fatal Error: New format database '{}' exists already - "
              "Remove it or use --append".format(dst_db))
        print("Note: --append must maintain an ID mapping and is much slower"
              "and requires sufficient memory to track all event IDs")
        return 1

    conn = sqlite3.connect(src_db)
    uri = args.uri or "sqlite:///{}".format(dst_db)

    engine = create_engine(uri, echo=False)
    models.Base.metadata.create_all(engine)
    session_factory = sessionmaker(bind=engine)
    session = session_factory()

    append = args.append or args.uri

    c = conn.cursor()
    c.execute("SELECT count(*) FROM recorder_runs")
    num_rows = c.fetchone()[0]
    print("Converting {} recorder_runs".format(num_rows))
    c.close()

    c = conn.cursor()
    n = 0
    for row in c.execute("SELECT * FROM recorder_runs"):  # type: ignore
        n += 1
        session.add(models.RecorderRuns(
            start=ts_to_dt(row[1]),
            end=ts_to_dt(row[2]),
            closed_incorrect=row[3],
            created=ts_to_dt(row[4])
        ))
        if n % 1000 == 0:
            session.commit()
            print_progress(n, num_rows)
    print_progress(n, num_rows)
    session.commit()
    c.close()

    c = conn.cursor()
    c.execute("SELECT count(*) FROM events")
    num_rows = c.fetchone()[0]
    print("Converting {} events".format(num_rows))
    c.close()

    id_mapping = {}

    c = conn.cursor()
    n = 0
    for row in c.execute("SELECT * FROM events"):  # type: ignore
        n += 1
        o = models.Events(
            event_type=row[1],
            event_data=row[2],
            origin=row[3],
            created=ts_to_dt(row[4]),
            time_fired=ts_to_dt(row[5]),
        )
        session.add(o)
        if append:
            session.flush()
            id_mapping[row[0]] = o.event_id
        if n % 1000 == 0:
            session.commit()
            print_progress(n, num_rows)
    print_progress(n, num_rows)
    session.commit()
    c.close()

    c = conn.cursor()
    c.execute("SELECT count(*) FROM states")
    num_rows = c.fetchone()[0]
    print("Converting {} states".format(num_rows))
    c.close()

    c = conn.cursor()
    n = 0
    for row in c.execute("SELECT * FROM states"):  # type: ignore
        n += 1
        session.add(models.States(
            entity_id=row[1],
            state=row[2],
            attributes=row[3],
            last_changed=ts_to_dt(row[4]),
            last_updated=ts_to_dt(row[5]),
            event_id=id_mapping.get(row[6], row[6]),
            domain=row[7]
        ))
        if n % 1000 == 0:
            session.commit()
            print_progress(n, num_rows)
    print_progress(n, num_rows)
    session.commit()
    c.close()
    return 0
