#!/usr/bin/python3
#
# Copyright (c) 2023-2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
#
# This script is used create a 2nd instance of postgres
# on deploy start step of an upgrade. It needs a port
# number as parameter that should be different from the
# default postgres port value.
#

import configparser
import grp
import logging as LOG
import os
import pwd
import shutil
import subprocess
import sys

import upgrade_utils


class PostgresDatabase:
    DEFAULT_POSTGRESQL_PORT = 5432
    POSTGRESQL_PATH = "/var/lib/postgresql"
    POSTGRESQL_RUNTIME = "/var/run/postgresql"
    BUILD_INFO_FILE = "/etc/build.info"

    def __init__(self, port):
        # get postgres uid and gid
        self._uid = pwd.getpwnam("postgres").pw_uid
        self._gid = grp.getgrnam("postgres").gr_gid

        # set database port
        if port == self.DEFAULT_POSTGRESQL_PORT:
            LOG.error(f"Port number should be different from the "
                      f"default {self.DEFAULT_POSTGRESQL_PORT}")
            raise
        self._port = port

        # set postgres bin dir
        try:
            process = subprocess.run(["pg_config", "--bindir"], check=True,
                                     text=True, capture_output=True)
            self._postgres_bin_dir = process.stdout.strip()
        except subprocess.CalledProcessError as e:
            LOG.error(f"Error getting postgres bindir: {str(e)}")
            raise

        # get sw_version
        try:
            cp = configparser.ConfigParser()
            default_section = configparser.DEFAULTSECT
            with open(self.BUILD_INFO_FILE, "r") as fp:
                cp.read_string(f"[{default_section}]\n" + fp.read())
                self._sw_version = cp.get(default_section, "SW_VERSION").strip('"')
        except Exception as e:
            LOG.error(f"Error getting SW_VERSION: {str(e)}")
            raise

        # set postgres data dir
        self._postgres_data_dir = os.path.join(self.POSTGRESQL_PATH, self._sw_version)

    def run(self):
        # create postgres data directory
        try:
            shutil.rmtree(self._postgres_data_dir, ignore_errors=True)
            os.makedirs(self._postgres_data_dir, mode=0o700, exist_ok=True)
            os.chown(self._postgres_data_dir, uid=self._uid, gid=self._gid)
        except Exception as e:
            LOG.error(f"Error setting up postgres data directory: {str(e)}")
            return 1
        LOG.info(f"Created postgres data directory {self._postgres_data_dir}")

        # initialize postgres instance
        try:
            cmd = ["sudo", "-u", "postgres", os.path.join(self._postgres_bin_dir, "initdb"),
                   "-D", self._postgres_data_dir]
            subprocess.run(cmd, check=True, text=True, capture_output=True)
        except subprocess.CalledProcessError as e:
            LOG.error(f"Failed to initialize the postgres database: {str(e.stderr)}")
            return 1
        LOG.info("Initialized new postgres database instance")

        # set password encryption
        try:
            cmd = ["sed", "-i", "s/^#\?password_encryption.*/password_encryption = 'scram-sha-256'/",
                   os.path.join(self._postgres_data_dir, "postgresql.conf")]
            subprocess.run(cmd, check=True, text=True, capture_output=True)
        except subprocess.CalledProcessError as e:
            LOG.error(f"Failed to set password encryption method: {str(e.stderr)}")
            return 1
        LOG.info("Database password encryption changed")

        # create postgres runtime directory
        try:
            os.makedirs(self.POSTGRESQL_RUNTIME, exist_ok=True)
            os.chown(self.POSTGRESQL_RUNTIME, uid=self._uid, gid=self._gid)
        except Exception as e:
            LOG.error(f"Error setting up postgres runtime directory: {str(e)}")
            return 1
        LOG.info(f"Created postgres runtime directory {self.POSTGRESQL_RUNTIME}")

        # start postgres instance
        try:
            cmd = ["sudo", "-u", "postgres", os.path.join(self._postgres_bin_dir, "pg_ctl"), "-D",
                       self._postgres_data_dir, "-o", f"-F -p {self._port}", "start"]
            subprocess.run(cmd, check=True)  # somehow capture_output hangs up the command
        except subprocess.CalledProcessError as e:
            LOG.error(f"Failed to start postgres database: {str(e)}")
            return 1
        LOG.info(f"Started new postgres database instance on port {self._port}")

        return 0


if __name__ == "__main__":
    upgrade_utils.configure_logging("/var/log/software.log", log_level=LOG.INFO)

    port = None
    error = False
    for arg in range(1, len(sys.argv)):
        if arg == 1:
            try:
                port = int(sys.argv[arg])
            except ValueError:
                error = True

    if port is None:
        usage_msg = f"usage: {sys.argv[0]} <port>"
        print(usage_msg)
        LOG.error(usage_msg)
        sys.exit(1)

    if error:
        error_msg = f"Invalid port: {port}"
        print(error_msg)
        LOG.error(error_msg)
        sys.exit(1)

    postgres_database = PostgresDatabase(port)
    sys.exit(postgres_database.run())