#! /bin/bash

set -euo pipefail 
SCRIPTPATH="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P)"

log_exec_params() {
  echo
  echo -n "$0 parameters: "
  for arg in "$@"; do echo -n "$arg "; done
  echo
}

log_exec_params "$@"


print_help () {
    echo "Usage: $0 [OPTION[=VALUE]]..."
    echo
    echo "Creates a patch script that allows to upgrade existing database holding HAF data to a new version without dropping it."
    echo "OPTIONS:"
    echo "  --host=VALUE         Allows to specify a PostgreSQL host location (defaults to /var/run/postgresql)"
    echo "  --port=NUMBER        Allows to specify a PostgreSQL operating port (defaults to 5432)"
    echo "  --haf-db-name=NAME   Allows to specify a name of database to store a HAF data"
    echo "  --haf-admin-account=NAME  Allows to specify a name of database admin role having permission to create the database"
    echo "                       and install an exension inside."
    echo "                       Role MUST be earlier created on pointed Postgres instance !!!"
    echo "                       If omitted, defaults to haf_admin role."
    echo
    echo "  --help               Display this help screen and exit"
    echo
}

psql_do() {
     sudo -Enu "$DB_ADMIN" psql -w $POSTGRES_ACCESS -v ON_ERROR_STOP=on -U "$DB_ADMIN" "$@"
}

DB_NAME="haf_block_log"
DB_ADMIN="haf_admin"
POSTGRES_HOST="/var/run/postgresql"
POSTGRES_PORT=5432


while [ $# -gt 0 ]; do
  case "$1" in
    --host=*)
        POSTGRES_HOST="${1#*=}"
        ;;
    --port=*)
        POSTGRES_PORT="${1#*=}"
        ;;
    --haf-db-name=*)
        DB_NAME="${1#*=}"
        ;;
    --haf-admin-account=*)
        DB_ADMIN="${1#*=}"
        ;;
    --help)
        print_help
        exit 0
        ;;
    -*)
        echo "ERROR: '$1' is not a valid option"
        echo
        print_help
        exit 1
        ;;
    *)
        echo "ERROR: '$1' is not a valid argument"
        echo
        print_help
        exit 2
        ;;
    esac
    shift
done

POSTGRES_ACCESS="--host $POSTGRES_HOST --port $POSTGRES_PORT"
COMMIT_PREV_ID=''
COMMIT_NEW_ID='@HAF_GIT_REVISION_SHA@'
POSTGRES_EXTENSION_DIR='@POSTGRES_SHAREDIR@/extension'
DB_NAME_AFTER_UPDATE='temp_after_update'

save_table_schema() {
  psql_do -d "$DB_NAME" -o before_update_columns.txt -q -t -A -c "SELECT table_name, table_columns FROM hive.calculate_schema_hash('hive')"
  psql_do -d "$DB_NAME" -o before_update_constraints.txt -q -t -A -c "SELECT table_name, table_constraints FROM hive.calculate_schema_hash('hive')"
  psql_do -d "$DB_NAME" -o before_update_indexes.txt -q -t -A -c "SELECT table_name, table_indexes FROM hive.calculate_schema_hash('hive')"
  psql_do -d "$DB_NAME_AFTER_UPDATE" -o after_update_columns.txt -q -t -A -c "SELECT table_name, table_columns FROM hive.calculate_schema_hash('hive')"
  psql_do -d "$DB_NAME_AFTER_UPDATE" -o after_update_constraings.txt -q -t -A -c "SELECT table_name, table_constraints FROM hive.calculate_schema_hash('hive')"
  psql_do -d "$DB_NAME_AFTER_UPDATE" -o after_update_indexes.txt -q -t -A -c "SELECT table_name, table_indexes FROM hive.calculate_schema_hash('hive')"
}

verify_table_schema() {
  echo "Attempting to verify if existing table schema is correct..."
  psql_do -a -d postgres -c "CREATE DATABASE $DB_NAME_AFTER_UPDATE WITH OWNER $DB_ADMIN;"
  psql_do -a -d "$DB_NAME_AFTER_UPDATE" -c 'CREATE EXTENSION hive_fork_manager CASCADE;'
  BEFORE_UPDATE=$(psql_do -d "$DB_NAME" -t -A -c "SELECT schema_hash FROM hive.create_database_hash('hive')")
  AFTER_UPDATE=$(psql_do -d "$DB_NAME_AFTER_UPDATE" -t -A -c "SELECT schema_hash FROM hive.create_database_hash('hive')")
  if [ "$BEFORE_UPDATE" = "$AFTER_UPDATE" ]; then
    echo "The table schema is correct, verification completed."
    echo "Dropping temporary database"
    psql_do -a -d postgres -c "DROP DATABASE IF EXISTS $DB_NAME_AFTER_UPDATE;"
  else
    save_table_schema
    echo "Table schema is inconsistent"
    echo "COLUMNS"
    diff --suppress-common-lines before_update_columns.txt after_update_columns.txt || true
    echo "CONSTRAINTS"
    diff --suppress-common-lines before_update_constraints.txt after_update_constraings.txt || true
    echo "INDEXES"
    diff --suppress-common-lines before_update_indexes.txt after_update_indexes.txt || true
    echo "Dropping temporary database"
    psql_do -a -d postgres -c "DROP DATABASE IF EXISTS $DB_NAME_AFTER_UPDATE;"
    find -type f -name '*.txt' > /dev/null 2>&1
    exit 1
  fi
}

get_deployed_version() {
  echo "Attempting to find version of already deployed hive_fork_manager extension..."

  COMMIT_PREV_ID=$(psql_do -d "$DB_NAME" -t -A -c "SELECT extversion FROM pg_extension WHERE extname = 'hive_fork_manager'")

  echo "Already deployed hive_fork_manager has a version: $COMMIT_PREV_ID"
}

generate_final_update_script() {
  echo
  echo "Attempting to generate update file..."
  pushd "${POSTGRES_EXTENSION_DIR}"

  # Postgres extension update rules require to be done only in incremental way by pointing a script hive_fork_manager--<from>--<to>.sql
  ln -svf "${POSTGRES_EXTENSION_DIR}/hive_fork_manager_update--$COMMIT_NEW_ID.sql" "hive_fork_manager--$COMMIT_PREV_ID--$COMMIT_NEW_ID.sql"

  popd
  echo "Update file was created correctly"
}

make_update() {
  echo
  echo "Attempting to update your database..."

  psql_do -d "$DB_NAME" -c "ALTER EXTENSION hive_fork_manager UPDATE"
}

save_views() {
  echo
  echo "Saving views referencing haf types..."

  query="
    CREATE TEMP TABLE allowed_types(name TEXT);
    INSERT INTO allowed_types(name) VALUES
      ('operation');
    CREATE TEMP TABLE allowed_domains(name TEXT);
    INSERT INTO allowed_domains(name) VALUES
      ('vest_amount'),
      ('hive_amount'),
      ('hbd_amount'),
      ('interest_rate');
    CREATE TEMP VIEW views AS
    SELECT DISTINCT col.table_schema::TEXT AS schema, col.table_name::TEXT AS name
      FROM information_schema.columns AS col
      JOIN pg_class as cls ON cls.relname = col.table_name
      WHERE
        cls.relkind = 'v' AND
        col.table_schema<>'hive' AND (
          (col.udt_schema='hive' AND col.udt_name NOT IN (SELECT name FROM allowed_types)) OR
          (col.domain_schema='hive' AND col.domain_name NOT IN (SELECT name FROM allowed_domains))
        );
    SELECT format('Saving %s.%s', v.schema, v.name), hive.deps_save_and_drop_dependencies(v.schema, v.name) FROM views AS v"
  psql_do -d "$DB_NAME" -q -t -A -F '' -c "$query"
}

save_materialized_views() {
  echo
  echo "Saving materialized views referencing haf types..."

  query="
    CREATE TEMP TABLE allowed_typenames(name TEXT);
    INSERT INTO allowed_typenames(name) VALUES
      ('operation'),
      ('vest_amount'),
      ('hive_amount'),
      ('hbd_amount'),
      ('interest_rate');
    CREATE TEMP VIEW views AS
    SELECT DISTINCT
      view_ns.nspname::TEXT AS schema,
      pg_class.relname::TEXT AS name
    FROM pg_catalog.pg_attribute
    JOIN pg_catalog.pg_class ON pg_class.oid = pg_attribute.attrelid
    JOIN pg_catalog.pg_namespace AS view_ns ON view_ns.oid = pg_class.relnamespace
    JOIN pg_catalog.pg_type AS col_type ON col_type.oid = pg_attribute.atttypid
    JOIN pg_catalog.pg_namespace AS col_ns ON col_ns.oid = col_type.typnamespace
    WHERE
      pg_class.relkind = 'm' AND
      NOT pg_attribute.attisdropped AND
      pg_attribute.attnum > 0 AND
      view_ns.nspname<>'hive' AND
      col_ns.nspname='hive' AND
      col_type.typname NOT IN (SELECT name FROM allowed_typenames);
    SELECT format('Saving %s.%s', v.schema, v.name), hive.deps_save_and_drop_dependencies(v.schema, v.name) FROM views AS v"
  psql_do -d "$DB_NAME" -q -t -A -F '' -c "$query"
}

restore_views() {
  echo
  echo "Restoring saved views..."

  query="
    SELECT
      format('Restoring %s.%s', deps_view_schema, deps_view_name),
      hive.deps_restore_dependencies(deps_view_schema, deps_view_name)
    FROM hive.deps_saved_ddl"
  psql_do -d "$DB_NAME" -q -t -A -F '' -c "$query" | uniq # do not print duplicates
}

check_tables_dont_reference_haf_types() {
  echo
  echo "Checking that none table references HAF type..."

  # hive.operation is explicitly ignored, because we never drop this type, so it's safe as a column.
  query="
    CREATE TEMP TABLE allowed_types(name TEXT);
    INSERT INTO allowed_types(name) VALUES
      ('operation');
    SELECT col.table_schema,col.table_name,col.column_name,col.udt_schema,col.udt_name
      FROM information_schema.columns AS col
      JOIN pg_class as cls ON cls.relname = col.table_name
      WHERE
        cls.relkind = 'r' AND
        col.udt_schema='hive' AND
        col.table_schema<>'hive' AND
        col.udt_name NOT IN (SELECT name FROM allowed_types)"
  psql_do -d "$DB_NAME" -q -t -A -c "$query" | \
    awk -F'|' '{print($1"."$2, "contains column", $3, "of type", $4"."$5, "which would be dropped on upgrade")} END{exit NR > 0 ? 4 : 0}'
}

check_tables_dont_reference_haf_domains() {
  echo
  echo "Checking that none table references HAF domain..."

  query="
    CREATE TEMP TABLE allowed_domains(name TEXT);
    INSERT INTO allowed_domains(name) VALUES
      ('vest_amount'),
      ('hive_amount'),
      ('hbd_amount'),
      ('interest_rate');
    SELECT col.table_schema,col.table_name,col.column_name,col.domain_schema,col.domain_name
      FROM information_schema.columns AS col
      JOIN pg_class as cls ON cls.relname = col.table_name
      WHERE
        cls.relkind = 'r' AND
        col.domain_schema='hive' AND
        col.table_schema<>'hive' AND
        col.domain_name NOT IN (SELECT name FROM allowed_domains)"
  psql_do -d "$DB_NAME" -q -t -A -c "$query" | \
    awk -F'|' '{print($1"."$2, "contains column", $3, "of type", $4"."$5, "which would be dropped on upgrade")} END{exit NR > 0 ? 4 : 0}'
}

check_functions_were_updated() {
  echo
  echo "Checking that all C functions were properly updated..."

  query="
  SELECT p.proname,p.prosrc,p.probin
    FROM pg_catalog.pg_proc AS p
    JOIN pg_catalog.pg_namespace AS n ON p.pronamespace=n.oid
    JOIN pg_catalog.pg_language AS l ON p.prolang=l.oid
    WHERE n.nspname='hive' AND l.lanname='c' AND p.probin NOT LIKE '%-$COMMIT_NEW_ID.so'"
  psql_do -d "$DB_NAME" -q -t -A -c "$query" | \
    awk -v "HASH=$COMMIT_NEW_ID" -F'|' '{print("Function", $1, "references", $2, "in", $3 ", but", HASH, "was expected")} END{exit NR > 0 ? 3 : 0}'
}

psql_do -d "$DB_NAME" -q -t -A -f "$SCRIPTPATH/hive_fork_manager_save_restore_views.sql"

verify_table_schema

get_deployed_version

generate_final_update_script

check_tables_dont_reference_haf_types

check_tables_dont_reference_haf_domains

save_views
save_materialized_views

make_update

restore_views

check_functions_were_updated
