#!/usr/bin/env python

# This file contains positive tests for push command

from __future__ import absolute_import
from __future__ import print_function
import os
import sys
# Extend that path so that we can import the module under test.
sys.path.insert(0, os.path.abspath('..'))

import wavectl

import unittest
import shutil
import datetime
import tempfile
import time
import copy
import logging
import datetime
import json
import random
import socket


from wavectl.BaseWavefrontCommand import BaseWavefrontCommand
import git

import util
import wavefront_api_client


class Test(util.TestMutate):

    def executePush(
            self,
            rsrcType,
            target,
            pushAdditionalParams=[],
            rsrcAdditionalParams=[]):

        logging.info("Starting executePush")

        assert os.path.isdir(target) or os.path.isfile(target) \
            and "The target of the push should be a directory or a file"

        args = ["push", target,
                "--wavefrontHost", util.wavefrontHostName,
                "--apiToken", util.wavefrontApiToken] \
            + pushAdditionalParams \
            + [rsrcType] \
            + rsrcAdditionalParams

        wc = wavectl.Wavectl(designForTestArgv=args)

        with util.StdoutCapture() as captOut:
            wc.runCmd()

        logging.info("Completed executePush")
        return captOut.str()

    def emptyPush(self, rsrcType, expectedOutRegex):
        """Execute a push operation without any entries in the directory.
        The wavectl actually does not send any PUSH requests in this case"""
        with util.TempDir() as td:
            d = td.dir()
            r = self.repoInit(d)
            self.addReadmeFileToRepo(r)

            out = self.executePush(
                rsrcType, d, pushAdditionalParams=["--inGit"])
            actualOut = out.strip().split("\n")
            util.SummaryLineProcessor.compareExpectedActualLineByLine(
                self, expectedOutRegex, actualOut)

    def test_emptyPush(self):
        expectedOut = [r"Replaced alert\(s\):"]
        expectedOut.append(r"ID\s*NAME\s*STATUS\s*SEVERITY")
        self.emptyPush("alert", expectedOut)

        expectedOut = [r"Replaced dashboard\(s\):"]
        expectedOut.append(r"ID\s*NAME\s*DESCRIPTION")
        self.emptyPush("dashboard", expectedOut)

    def noChangePush(self, rsrcType, rsrcs, expectedOutRegex):
        """ First do a pull and then do a push without changing any of the files"""
        with util.TempDir() as td:
            d = td.dir()
            r = self.repoInit(d)

            self.addReadmeFileToRepo(r)
            # First execute a pull to populate a directory with the resources.
            self.executePull(rsrcType, d, r,
                             rsrcs, pullAdditionalParams=["--inGit"])

            out = self.executePush(
                rsrcType, d, pushAdditionalParams=["--inGit"])
            actualOut = out.strip().split("\n")
            util.SummaryLineProcessor.compareExpectedActualLineByLine(
                self, expectedOutRegex, actualOut)

    def test_noChangePush(self):
        expectedOut = [r"Replaced alert\(s\):"]
        expectedOut.append(r"ID\s*NAME\s*STATUS\s*SEVERITY")
        for r in util.allAlerts:
            expectedOut.append(
                util.SummaryLineProcessor.expectedAlertSummaryLineRegex(r,
                                                                        # Status line is purged by wavectl pull. It is not an attribute
                                                                        # that the user can modify. A pushed rsrc will also not
                                                                        # have it because push uses the files generated by a previous
                                                                        # pull in this test.
                                                                        ignoreStatus=True))
        self.noChangePush("alert", util.allAlerts, expectedOut)

        expectedOut = [r"Replaced dashboard\(s\):"]
        expectedOut.append(r"ID\s*NAME\s*DESCRIPTION")
        for r in util.allDashboards:
            expectedOut.append(
                util.SummaryLineProcessor.expectedDashboardSummaryLineRegex(r))
        self.noChangePush("dashboard", util.allDashboards, expectedOut)

    def pushWithCustomerTag(self, rsrcType, rsrcs, rsrcAdditionalParams,
                            expectedOutRegex, callFromParent=False):
        """The push operation has multiple cusomer tags. The tags should be
        effectively AND'ed and resources that match all tags should be sent
        in the push request. That is the same behavior when we are passing tags
        to get requests to the webfront server"""

        with util.TempDir() as td:
            d = td.dir()
            r = self.repoInit(d)

            self.addReadmeFileToRepo(r)
            self.executePull(
                rsrcType,
                d,
                r,
                rsrcs,
                pullAdditionalParams=["--inGit"])

            # In some test cases we execute this test from another
            # directory than the directory with the resources.
            if callFromParent:
                parentDir = os.path.dirname(d)
                dirName = os.path.basename(d)
            else:
                dirName = d

            with util.CwdChanger(parentDir) if callFromParent \
                    else util.DummyContextManager() as cc:

                out = self.executePush(
                    rsrcType,
                    dirName,
                    pushAdditionalParams=["--inGit"],
                    rsrcAdditionalParams=rsrcAdditionalParams)
                actualOut = out.strip().split("\n")
                util.SummaryLineProcessor.compareExpectedActualLineByLine(
                    self, expectedOutRegex, actualOut)

    def test_pushWithCustomerTag(self):
        """Use --customerTags to narrow down the pushed alerts to wavefront.
        Execute push operations from the current dir of the resources and
        from the parent dir."""

        # Alerts that get pushed because only these are matched by
        # the passed customerTags
        matchedRsrcs = [
            util.Alert.kubernetesSkynetTag,
        ]
        expectedOut = [r"Replaced alert\(s\):"]
        expectedOut.append(r"ID\s*NAME\s*STATUS\s*SEVERITY")
        for r in matchedRsrcs:
            expectedOut.append(
                util.SummaryLineProcessor.expectedAlertSummaryLineRegex(
                    r, ignoreStatus=True))
        self.pushWithCustomerTag("alert",
                                 util.allAlerts,
                                 ["--customerTag", "skynet",
                                  "--customerTag", "kubernetes"],
                                 expectedOut)
        self.pushWithCustomerTag("alert",
                                 util.allAlerts,
                                 ["--customerTag", "skynet",
                                  "--customerTag", "kubernetes"],
                                 expectedOut,
                                 callFromParent=True)

        # Dashboards that get pushed because only these are matched by
        # the passed customerTags
        matchedRsrcs = [
            util.Dashboard.skynetApplier,
        ]
        expectedOut = [r"Replaced dashboard\(s\):"]
        expectedOut.append(r"ID\s*NAME\s*DESCRIPTION")
        for r in matchedRsrcs:
            expectedOut.append(
                util.SummaryLineProcessor.expectedDashboardSummaryLineRegex(r))
        self.pushWithCustomerTag("dashboard",
                                 util.allDashboards,
                                 ["--customerTag", "skynet",
                                  "--customerTag", "kubernetes"],
                                 expectedOut)
        self.pushWithCustomerTag("dashboard",
                                 util.allDashboards,
                                 ["--customerTag", "skynet",
                                  "--customerTag", "kubernetes"],
                                 expectedOut,
                                 callFromParent=True)

    (index, workingTree) = range(2)

    def repoIsDirtyInDifferentDir(
            self,
            rsrcType,
            rsrcs,
            whatIsDirty,
            expectedOutRegex):
        """The repo is dirty in a different sub-dir than the subdir used for
        push. Depending on the whatIsDirty value, either the index is dirty or the
        working tree is dity"""
        with util.TempDir() as td:
            d = td.dir()
            r = self.repoInit(d)

            self.addReadmeFileToRepo(r)

            cleanSubdirName = "cleanSubdir"
            cleanSubdir = os.path.join(d, cleanSubdirName)
            # The pull will also create the cleanSubdir
            self.executePull(rsrcType, cleanSubdir, r, rsrcs,
                             pullAdditionalParams=["--inGit"])

            dirtySubdirName = "dirtySubdir"
            dirtySubdir = os.path.join(d, dirtySubdirName)
            os.mkdir(dirtySubdir)

            newFileName = "file1"
            self.addNewFileToRepo(r, newFileName, subdir=dirtySubdirName)

            if whatIsDirty == self.index:
                pass
            elif whatIsDirty == self.workingTree:
                # In order to make the working tree dirty we commit the file
                # and make a local modification on it.
                r.index.commit(
                    "Initial commit of {0} file".format(newFileName),
                    skip_hooks=True)

                fp = os.path.join(d, dirtySubdirName, newFileName)
                with open(fp, "r+") as f:
                    f.write("Some new modification")
            else:
                assert not "Unexpected value in whatIsDirty"

            assert(r.is_dirty())

            out = self.executePush(rsrcType, cleanSubdir,
                                   pushAdditionalParams=["--inGit"])
            actualOut = out.strip().split("\n")
            util.SummaryLineProcessor.compareExpectedActualLineByLine(
                self, expectedOutRegex, actualOut)

    def test_repoIsDirtyInDifferentDir(self):
        """The repo is dirty in a different sub directory than the
        subdirectory used for push. Push should still succeed.
        We verify both cases:
        1) repo has staged but uncommitted files (index is dirty) in a
        different sub dir
        2) repo has local modifications (non-staged) in a different subdir"""

        expectedOut = [r"Replaced alert\(s\):"]
        expectedOut.append(r"ID\s*NAME\s*STATUS\s*SEVERITY")
        for r in util.allAlerts:
            expectedOut.append(
                util.SummaryLineProcessor.expectedAlertSummaryLineRegex(
                    r, ignoreStatus=True))
        self.repoIsDirtyInDifferentDir(
            "alert", util.allAlerts, self.index, expectedOut)
        self.repoIsDirtyInDifferentDir(
            "alert", util.allAlerts, self.workingTree, expectedOut)

        expectedOut = [r"Replaced dashboard\(s\):"]
        expectedOut.append(r"ID\s*NAME\s*DESCRIPTION")
        for r in util.allDashboards:
            expectedOut.append(
                util.SummaryLineProcessor.expectedDashboardSummaryLineRegex(r))
        self.repoIsDirtyInDifferentDir("dashboard", util.allDashboards,
                                       self.index, expectedOut)
        self.repoIsDirtyInDifferentDir("dashboard", util.allDashboards,
                                       self.workingTree, expectedOut)

    def pushSingleFile(self, rsrcType, rsrcs, pushedRsrc, matchParams,
                       expectedOutRegex):
        """ Select one file to push. Only the resource represented in that file
        should be pushed to the wavefront server. If pushMatchesTargetFile parameter
        is false, the push command will have accompanying match parameters that
        will not match with the resource in the file. The single file
        push attempt will actually not write anything to the wavefront
        server"""

        with util.TempDir() as td:
            d = td.dir()
            r = self.repoInit(d)

            self.addReadmeFileToRepo(r)
            self.executePull(
                rsrcType,
                d,
                r,
                rsrcs,
                pullAdditionalParams=["--inGit"])

            # Compile the full path of the single file to be pushed.
            targetRsrc = pushedRsrc
            rt = util.resourceTypeFromString(rsrcType)
            fileName = str(targetRsrc[rt._uniqueKey]) + rt.fileExtension()
            target = os.path.join(d, fileName)

            out = self.executePush(rsrcType, target,
                                   pushAdditionalParams=["--inGit"],
                                   rsrcAdditionalParams=matchParams)
            actualOut = out.strip().split("\n")
            util.SummaryLineProcessor.compareExpectedActualLineByLine(
                self, expectedOutRegex, actualOut)

    def test_pushSingleFileWithMatch(self):
         # Single alert that get pushed
        pushedRsrc = util.Alert.kubernetesSkynetTag
        expectedOut = [r"Replaced alert\(s\):"]
        expectedOut.append(r"ID\s*NAME\s*STATUS\s*SEVERITY")
        expectedOut.append(
            util.SummaryLineProcessor.expectedAlertSummaryLineRegex(
                pushedRsrc, ignoreStatus=True))

        self.pushSingleFile("alert",
                            util.allAlerts,
                            pushedRsrc,
                            [],
                            expectedOut)

        # Single dashboard that get pushed
        pushedRsrc = util.Dashboard.kubeBoxPki
        expectedOut = [r"Replaced dashboard\(s\):"]
        expectedOut.append(r"ID\s*NAME\s*DESCRIPTION")
        expectedOut.append(
            util.SummaryLineProcessor.expectedDashboardSummaryLineRegex(pushedRsrc))
        self.pushSingleFile("dashboard",
                            util.allDashboards,
                            pushedRsrc,
                            [],
                            expectedOut)

    def test_pushSingleFileWithMismatch(self):
        """ A single file is pushed. But the match parameters do not match
        with the resource represented in that file"""

        # Single alert that get pushed
        pushedRsrc = util.Alert.kubernetesSkynetTag
        expectedOut = [r"Replaced alert\(s\):"]
        expectedOut.append(r"ID\s*NAME\s*STATUS\s*SEVERITY")
        self.pushSingleFile(
            "alert", util.allAlerts, pushedRsrc, [
                "--name", "someNameDoesNotMatch_j4h5gh45kj2h34k5jh"], expectedOut)

        # Single dashboard that get pushed
        pushedRsrc = util.Dashboard.kubeBoxPki
        expectedOut = [r"Replaced dashboard\(s\):"]
        expectedOut.append(r"ID\s*NAME\s*DESCRIPTION")
        self.pushSingleFile(
            "dashboard", util.allDashboards, pushedRsrc, [
                "--name", "someNameDoesNotMatch_j4h5gh45kj2h34k5jh"], expectedOut)


if __name__ == '__main__':
    #  util.initLog()
    util.unittestMain()
