# Copyright (c) 2013 OpenStack Foundation
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import os
import re
import stat

from oslo_log import log as logging

from trove.common import cfg
from trove.common import exception
from trove.common.i18n import _
from trove.common import utils
from trove.guestagent.common import operating_system
from trove.guestagent.common.operating_system import FileMode
from trove.guestagent.datastore.postgresql.service import PgSqlApp
from trove.guestagent.strategies.backup import base

CONF = cfg.CONF
LOG = logging.getLogger(__name__)
WAL_ARCHIVE_DIR = CONF.postgresql.wal_archive_location


class PgDump(base.BackupRunner):
    """Implementation of Backup Strategy for pg_dump."""
    __strategy_name__ = 'pg_dump'

    @property
    def cmd(self):
        cmd = 'sudo -u postgres pg_dumpall '
        return cmd + self.zip_cmd + self.encrypt_cmd


class PgBaseBackupUtil(object):

    def most_recent_backup_wal(self, pos=0):
        """
        Return the WAL file for the most recent backup
        """
        mrb = self.most_recent_backup_file(pos=pos)
        return mrb.split(".")[0]

    def most_recent_backup_file(self, pos=0):
        """
        Look for the most recent .backup file that basebackup creates
        :return: a string like 000000010000000000000006.00000168.backup
        """
        walre = re.compile("[0-9A-F]{24}.*.backup")
        b = [f for f in os.listdir(WAL_ARCHIVE_DIR)
             if walre.search(f)]
        b = sorted(b, reverse=True)
        if not b:
            return None
        return b[pos]

    def log_files_since_last_backup(self, pos=0):
        """Return the WAL files since the provided last backup
        pg_archivebackup depends on alphanumeric sorting to decide wal order,
        so we'll do so too:
        https://github.com/postgres/postgres/blob/REL9_4_STABLE/contrib
           /pg_archivecleanup/pg_archivecleanup.c#L122
        """
        last_wal = self.most_recent_backup_wal(pos=pos)
        d = os.listdir(WAL_ARCHIVE_DIR)
        LOG.info("Using %s for most recent wal file" % last_wal)
        LOG.info("wal archive dir contents" + str(d))
        walre = re.compile("^[0-9A-F]{24}$")
        wal = [f for f in d
               if walre.search(f) and f >= last_wal]
        return wal


class PgBaseBackup(base.BackupRunner, PgBaseBackupUtil):
    """Base backups are taken with the pg_basebackup filesystem-level backup
     tool pg_basebackup creates a copy of the binary files in the PostgreSQL
     cluster data directory and enough WAL segments to allow the database to
     be brought back to a consistent state. Associated with each backup is a
     log location, normally indicated by the WAL file name and the position
     inside the file.
     """
    __strategy_name__ = 'pg_basebackup'

    def __init__(self, *args, **kwargs):
        self._app = None
        super(PgBaseBackup, self).__init__(*args, **kwargs)
        self.label = None
        self.stop_segment = None
        self.start_segment = None
        self.start_wal_file = None
        self.stop_wal_file = None
        self.checkpoint_location = None
        self.mrb = None

    @property
    def app(self):
        if self._app is None:
            self._app = self._build_app()
        return self._app

    def _build_app(self):
        return PgSqlApp()

    @property
    def cmd(self):
        cmd = "pg_basebackup -h %s -U %s --pgdata=-" \
              " --label=%s --format=tar --xlog " % \
              (self.app.pgsql_run_dir, self.app.ADMIN_USER,
               self.base_filename)

        return cmd + self.zip_cmd + self.encrypt_cmd

    def base_backup_metadata(self, f):
        """Parse the contents of the .backup file"""
        meta = {}
        operating_system.chmod(f, FileMode(add=[stat.S_IROTH]), as_root=True)

        start_re = re.compile("START WAL LOCATION: (.*) \(file (.*)\)")
        stop_re = re.compile("STOP WAL LOCATION: (.*) \(file (.*)\)")
        checkpt_re = re.compile("CHECKPOINT LOCATION: (.*)")
        label_re = re.compile("LABEL: (.*)")

        with open(f, 'r') as base_metadata:
            lines = "\n".join(base_metadata.readlines())

            match = start_re.search(lines)
            if match:
                self.start_segment = meta['start-segment'] = match.group(1)
                self.start_wal_file = meta['start-wal-file'] = match.group(2)

            match = stop_re.search(lines)
            if match:
                self.stop_segment = meta['stop-segment'] = match.group(1)
                self.stop_wal_file = meta['stop-wal-file'] = match.group(2)

            match = checkpt_re.search(lines)
            if match:
                self.checkpoint_location \
                    = meta['checkpoint-location'] = match.group(1)

            match = label_re.search(lines)
            if match:
                self.label = meta['label'] = match.group(1)
        return meta

    def check_process(self):
        """If any of the below variables were not set by either metadata()
           or direct retrieval from the pgsql backup commands, then something
           has gone wrong
        """
        if not self.start_segment or not self.start_wal_file:
            LOG.info("Unable to determine starting WAL file/segment")
            return False
        if not self.stop_segment or not self.stop_wal_file:
            LOG.info("Unable to determine ending WAL file/segment")
            return False
        if not self.label:
            LOG.info("No backup label found")
            return False
        return True

    def metadata(self):