#!/usr/bin/env python2

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""
Script for rebranding of database to and from what Kallithea expects

Works on databases from v1.7.2 to v2.2.5
"""

import sys
from sqlalchemy import *
import sqlalchemy.orm
import sqlalchemy.ext.declarative
import migrate.changeset # a part of sqlalchemy-migrate which is available on pypi

def do_migrate(db, old, new):
    print 'Migrating %s from %s to %s' % (db, old or '?', new)
    metadata = MetaData()
    metadata.bind = create_engine(db)
    metadata.reflect()
    assert metadata.tables, 'Cannot reflect table names from db'

    if not old:
        assert 'db_migrate_version' in metadata.tables, 'Cannot reflect db_migrate_version from db'
        t = metadata.tables['db_migrate_version']
        l = t.select().where(t.c.repository_path == 'versions').execute().fetchall()
        assert len(l) == 1, 'Cannot find a single versions entry in db_migrate_version'
        assert l[0].repository_id.endswith('_db_migrations')
        old = l[0].repository_id[:-len('_db_migrations')]
        print 'Detected migration from old name %s' % old
        if new != old:
            assert not t.select().where(t.c.repository_id == new + '_db_migrations').execute().fetchall(), 'db_migrate_version has entries for both old and new name'

    def tablename(brand, s):
        return s if brand == 'kallithea' else (brand + '_' + s)
    new_ui_name = tablename(new, 'ui')
    old_ui_name = tablename(old, 'ui')
    new_settings_name = tablename(new, 'settings')
    old_settings_name = tablename(old, 'settings')

    # Table renames using sqlalchemy-migrate (available on pypi)
    if new_ui_name == old_ui_name:
        print 'No renaming of %s' % new_ui_name
    else:
        try:
            t = metadata.tables[old_ui_name]
            print 'Renaming', t, 'to', new_ui_name
            migrate.changeset.rename_table(t, new_ui_name)
        except KeyError as e:
            print 'Not renaming ui:', e

    if new_settings_name == old_settings_name:
        print 'No renaming of %s' % new_settings_name
    else:
        try:
            t = metadata.tables[old_settings_name]
            print 'Renaming', t, 'to', new_settings_name
            migrate.changeset.rename_table(t, new_settings_name)
        except KeyError as e:
            print 'Not renaming settings:', e

    old_auth_name = 'internal' if old == 'kallithea' else old
    new_auth_name = 'internal' if new == 'kallithea' else new

    # using this API because ... dunno ... it is simple and works
    conn = metadata.bind.connect()
    trans = conn.begin()
    t = metadata.tables['users']

    print 'Bulk fixing of User extern_name'
    try:
        t.c.extern_name
    except AttributeError:
        print 'No extern_name to rename'
    else:
        t.update().where(t.c.extern_name == old_auth_name).values(extern_name=new_auth_name).execute()

    print 'Bulk fixing of User extern_type'
    try:
        t.c.extern_type
    except AttributeError:
        print 'No extern_type to rename'
    else:
        t.update().where(t.c.extern_type == old_auth_name).values(extern_type=new_auth_name).execute()

    trans.commit()

    # For the following conversions, use ORM ... and create stub models that works for that purpose
    Base = sqlalchemy.ext.declarative.declarative_base()

    class Ui(Base):
        __tablename__ = new_ui_name
        ui_id = Column("ui_id", Integer(), primary_key=True)
        ui_section = Column("ui_section", String())
        ui_key = Column("ui_key", String())
        ui_value = Column("ui_value", String())
        ui_active = Column("ui_active", Boolean())

    class Setting(Base):
        __tablename__ = new_settings_name
        app_settings_id = Column("app_settings_id", Integer(), primary_key=True)
        app_settings_name = Column("app_settings_name", String())
        app_settings_value = Column("app_settings_value", String())
        #app_settings_type = Column("app_settings_type", String()) # not present in v1.7.2

    class DbMigrateVersion(Base):
        __tablename__ = 'db_migrate_version'
        repository_id = Column('repository_id', String(), primary_key=True)
        repository_path = Column('repository_path', Text)
        version = Column('version', Integer)

    Session = sqlalchemy.orm.sessionmaker(bind=metadata.bind)
    session = Session()

    print 'Fixing hook names'

    oldhooks = u'python:%s.lib.hooks.' % old
    newhooks = u'python:%s.lib.hooks.' % new
    for u in session.query(Ui).filter(Ui.ui_section == 'hooks').all():
        if u.ui_value.startswith(oldhooks):
            print '- fixing %s' % u.ui_key
            u.ui_value = newhooks + u.ui_value[len(oldhooks):]
            session.add(u)
    session.commit()

    print 'Fixing auth module names'
    for s in session.query(Setting).filter(Setting.app_settings_name == 'auth_plugins').all():
        print '- fixing %s' % s.app_settings_name
        s.app_settings_value = (s.app_settings_value
                                .replace(old + '.lib.auth_modules.auth_', new + '.lib.auth_modules.auth_')
                                .replace('.auth_modules.auth_' + old_auth_name, '.auth_modules.auth_' + new_auth_name))
        session.add(s)
    for s in session.query(Setting).filter(Setting.app_settings_name == 'auth_' + old_auth_name + '_enabled').all():
        print '- fixing %s' % s.app_settings_name
        s.app_settings_name = 'auth_' + new_auth_name + '_enabled'
        session.add(s)
    session.commit()

    print 'Fixing db migration version number'
    for s in session.query(DbMigrateVersion).filter(DbMigrateVersion.repository_id == old + '_db_migrations', DbMigrateVersion.repository_path == 'versions').all():
        print '- fixing %s' % s.repository_id
        s.repository_id = new + '_db_migrations'
    session.commit()

    print 'Done'

def main(argv):
    if len(argv) < 2 or argv[1] in ['-h', '--help']:
        print 'usage: kallithea/bin/rebranddb.py DBSTRING [NEW] [OLD]'
        print '  where DBSTRING is the value of sqlalchemy.db1.url from the .ini,'
        print '  NEW defaults to "kallithea", OLD is by default detected from the db"'
        raise SystemExit(0)
    new = 'kallithea'
    if len(argv) > 2:
        new = argv[2]
    old = None
    if len(argv) > 3:
        old = argv[3]
    do_migrate(argv[1], old, new)

if __name__ == '__main__':
    main(sys.argv)
