#!/usr/bin/env python

import os
import shutil
from tempfile import mkstemp
from tempfile import mkdtemp
from ansible.playbook.play import Play
import ansible

import unittest
from nose.plugins.skip import SkipTest


class FakeCallBacks(object):
    def __init__(self):
        pass
    def on_vars_prompt(self):
        pass
    def on_import_for_host(self, host, filename):
        pass

class FakeInventory(object):
    def __init__(self):
        self.hosts = {}
    def basedir(self):
        return "."        
    def get_variables(self, host, vault_password=None):
        if host in self.hosts:
            return self.hosts[host]        
        else:
            return {}            

class FakePlayBook(object):
    def __init__(self):
        self.extra_vars = {}
        self.remote_user = None
        self.remote_port = None
        self.sudo = None
        self.sudo_user = None
        self.su = None
        self.su_user = None
        self.transport = None
        self.only_tags = None
        self.skip_tags = None
        self.VARS_CACHE = {}
        self.SETUP_CACHE = {}
        self.inventory = FakeInventory()
        self.callbacks = FakeCallBacks()

        self.VARS_CACHE['localhost'] = {}


class TestMe(unittest.TestCase):

    ########################################
    # BASIC FILE LOADING BEHAVIOR TESTS
    ########################################

    def test_play_constructor(self):
        # __init__(self, playbook, ds, basedir, vault_password=None)
        playbook = FakePlayBook()
        ds = { "hosts": "localhost"}
        basedir = "."
        play = Play(playbook, ds, basedir)

    def test_vars_file(self):

        # make a vars file
        fd, temp_path = mkstemp()
        f = open(temp_path, "wb")
        f.write("foo: bar\n")
        f.close()

        # create a play with a vars_file
        playbook = FakePlayBook()
        ds = { "hosts": "localhost",
               "vars_files": [temp_path]}
        basedir = "."
        play = Play(playbook, ds, basedir)
        os.remove(temp_path)

        # make sure the variable was loaded
        assert 'foo' in play.vars, "vars_file was not loaded into play.vars"
        assert play.vars['foo'] == 'bar', "foo was not set to bar in play.vars"

    def test_vars_file_nonlist_error(self):

        # make a vars file
        fd, temp_path = mkstemp()
        f = open(temp_path, "wb")
        f.write("foo: bar\n")
        f.close()

        # create a play with a string for vars_files
        playbook = FakePlayBook()
        ds = { "hosts": "localhost",
               "vars_files": temp_path}
        basedir = "."
        error_hit = False
        try:
            play = Play(playbook, ds, basedir)
        except:
            error_hit = True
        os.remove(temp_path)

        assert error_hit == True, "no error was thrown when vars_files was not a list"


    def test_multiple_vars_files(self):

        # make a vars file
        fd, temp_path = mkstemp()
        f = open(temp_path, "wb")
        f.write("foo: bar\n")
        f.close()

        # make a second vars file
        fd, temp_path2 = mkstemp()
        f = open(temp_path2, "wb")
        f.write("baz: bang\n")
        f.close()


        # create a play with two vars_files
        playbook = FakePlayBook()
        ds = { "hosts": "localhost",
               "vars_files": [temp_path, temp_path2]}
        basedir = "."
        play = Play(playbook, ds, basedir)
        os.remove(temp_path)
        os.remove(temp_path2)

        # make sure the variables were loaded
        assert 'foo' in play.vars, "vars_file was not loaded into play.vars"
        assert play.vars['foo'] == 'bar', "foo was not set to bar in play.vars"
        assert 'baz' in play.vars, "vars_file2 was not loaded into play.vars"
        assert play.vars['baz'] == 'bang', "baz was not set to bang in play.vars"

    def test_vars_files_first_found(self):

        # make a vars file
        fd, temp_path = mkstemp()
        f = open(temp_path, "wb")
        f.write("foo: bar\n")
        f.close()

        # get a random file path        
        fd, temp_path2 = mkstemp()
        # make sure this file doesn't exist
        os.remove(temp_path2)

        # create a play
        playbook = FakePlayBook()
        ds = { "hosts": "localhost",
               "vars_files": [[temp_path2, temp_path]]}
        basedir = "."
        play = Play(playbook, ds, basedir)
        os.remove(temp_path)

        # make sure the variable was loaded
        assert 'foo' in play.vars, "vars_file was not loaded into play.vars"
        assert play.vars['foo'] == 'bar', "foo was not set to bar in play.vars"

    def test_vars_files_multiple_found(self):

        # make a vars file
        fd, temp_path = mkstemp()
        f = open(temp_path, "wb")
        f.write("foo: bar\n")
        f.close()

        # make a second vars file
        fd, temp_path2 = mkstemp()
        f = open(temp_path2, "wb")
        f.write("baz: bang\n")
        f.close()

        # create a play
        playbook = FakePlayBook()
        ds = { "hosts": "localhost",
               "vars_files": [[temp_path, temp_path2]]}
        basedir = "."
        play = Play(playbook, ds, basedir)
        os.remove(temp_path)
        os.remove(temp_path2)

        # make sure the variables were loaded
        assert 'foo' in play.vars, "vars_file was not loaded into play.vars"
        assert play.vars['foo'] == 'bar', "foo was not set to bar in play.vars"
        assert 'baz' not in play.vars, "vars_file2 was loaded after vars_file1 was loaded"

    def test_vars_files_assert_all_found(self):

        # make a vars file
        fd, temp_path = mkstemp()
        f = open(temp_path, "wb")
        f.write("foo: bar\n")
        f.close()

        # make a second vars file
        fd, temp_path2 = mkstemp()
        # make sure it doesn't exist
        os.remove(temp_path2)

        # create a play
        playbook = FakePlayBook()
        ds = { "hosts": "localhost",
               "vars_files": [temp_path, temp_path2]}
        basedir = "."

        error_hit = False
        error_msg = None

        try:
            play = Play(playbook, ds, basedir)
        except ansible.errors.AnsibleError, e:
            error_hit = True
            error_msg = e

        os.remove(temp_path)
        assert error_hit == True, "no error was thrown for missing vars_file"


    ########################################
    # VARIABLE PRECEDENCE TESTS
    ########################################

    # On the first run vars_files are loaded into play.vars by host == None
    #   * only files with vars from host==None will work here
    # On the secondary run(s), a host is given and the vars_files are loaded into VARS_CACHE
    #   * this only occurs if host is not None, filename2 has vars in the name, and filename3 does not

    # filename  -- the original string
    # filename2 -- filename templated with play vars
    # filename3 -- filename2 template with inject (hostvars + setup_cache + vars_cache)
    # filename4 -- path_dwim(filename3)

    def test_vars_files_for_host(self):

        # host != None
        # vars in filename2
        # no vars in filename3

        # make a vars file
        fd, temp_path = mkstemp()
        f = open(temp_path, "wb")
        f.write("foo: bar\n")
        f.close()

        # build play attributes
        playbook = FakePlayBook()
        ds = { "hosts": "localhost",
               "vars_files": ["{{ temp_path }}"]}
        basedir = "."
        playbook.VARS_CACHE['localhost']['temp_path'] = temp_path

        # create play and do first run        
        play = Play(playbook, ds, basedir)

        # the second run is started by calling update_vars_files        
        play.update_vars_files(['localhost'])
        os.remove(temp_path)

        assert 'foo' in play.playbook.VARS_CACHE['localhost'], "vars_file vars were not loaded into vars_cache"
        assert play.playbook.VARS_CACHE['localhost']['foo'] == 'bar', "foo does not equal bar"

    def test_vars_files_for_host_with_extra_vars(self):

        # host != None
        # vars in filename2
        # no vars in filename3

        # make a vars file
        fd, temp_path = mkstemp()
        f = open(temp_path, "wb")
        f.write("foo: bar\n")
        f.close()

        # build play attributes
        playbook = FakePlayBook()
        ds = { "hosts": "localhost",
               "vars_files": ["{{ temp_path }}"]}
        basedir = "."
        playbook.VARS_CACHE['localhost']['temp_path'] = temp_path
        playbook.extra_vars = {"foo": "extra"}

        # create play and do first run        
        play = Play(playbook, ds, basedir)

        # the second run is started by calling update_vars_files        
        play.update_vars_files(['localhost'])
        os.remove(temp_path)

        assert 'foo' in play.vars, "extra vars were not set in play.vars"
        assert 'foo' in play.playbook.VARS_CACHE['localhost'], "vars_file vars were not loaded into vars_cache"
        assert play.playbook.VARS_CACHE['localhost']['foo'] == 'extra', "extra vars did not overwrite vars_files vars"


    ########################################
    # COMPLEX FILENAME TEMPLATING TESTS
    ########################################

    def test_vars_files_two_vars_in_name(self):

        # self.vars = ds['vars']
        # self.vars += _get_vars() ... aka extra_vars

        # make a temp dir
        temp_dir = mkdtemp()

        # make a temp file
        fd, temp_file = mkstemp(dir=temp_dir)
        f = open(temp_file, "wb")
        f.write("foo: bar\n")
        f.close()

        # build play attributes
        playbook = FakePlayBook()
        ds = { "hosts": "localhost",
               "vars": { "temp_dir": os.path.dirname(temp_file),
                         "temp_file": os.path.basename(temp_file) },
               "vars_files": ["{{ temp_dir + '/' + temp_file }}"]}
        basedir = "."

        # create play and do first run        
        play = Play(playbook, ds, basedir)

        # cleanup
        shutil.rmtree(temp_dir)

        assert 'foo' in play.vars, "double var templated vars_files filename not loaded"
    
    def test_vars_files_two_vars_different_scope(self):

        #
        # Use a play var and an inventory var to create the filename
        #

        # self.playbook.inventory.get_variables(host)
        #   {'group_names': ['ungrouped'], 'inventory_hostname': 'localhost', 
        #   'ansible_ssh_user': 'root', 'inventory_hostname_short': 'localhost'}

        # make a temp dir
        temp_dir = mkdtemp()

        # make a temp file
        fd, temp_file = mkstemp(dir=temp_dir)
        f = open(temp_file, "wb")
        f.write("foo: bar\n")
        f.close()

        # build play attributes
        playbook = FakePlayBook()
        playbook.inventory.hosts['localhost'] = {'inventory_hostname': os.path.basename(temp_file)}
        ds = { "hosts": "localhost",
               "vars": { "temp_dir": os.path.dirname(temp_file)},
               "vars_files": ["{{ temp_dir + '/' + inventory_hostname }}"]}
        basedir = "."

        # create play and do first run        
        play = Play(playbook, ds, basedir)

        # do the host run        
        play.update_vars_files(['localhost'])

        # cleanup
        shutil.rmtree(temp_dir)

        assert 'foo' not in play.vars, \
            "mixed scope vars_file loaded into play vars"
        assert 'foo' in play.playbook.VARS_CACHE['localhost'], \
            "differently scoped templated vars_files filename not loaded"
        assert play.playbook.VARS_CACHE['localhost']['foo'] == 'bar', \
            "foo is not bar"    

    def test_vars_files_two_vars_different_scope_first_found(self):

        #
        # Use a play var and an inventory var to create the filename
        #

        # make a temp dir
        temp_dir = mkdtemp()

        # make a temp file
        fd, temp_file = mkstemp(dir=temp_dir)
        f = open(temp_file, "wb")
        f.write("foo: bar\n")
        f.close()

        # build play attributes
        playbook = FakePlayBook()
        playbook.inventory.hosts['localhost'] = {'inventory_hostname': os.path.basename(temp_file)}
        ds = { "hosts": "localhost",
               "vars": { "temp_dir": os.path.dirname(temp_file)},
               "vars_files": [["{{ temp_dir + '/' + inventory_hostname }}"]]}
        basedir = "."

        # create play and do first run        
        play = Play(playbook, ds, basedir)

        # do the host run        
        play.update_vars_files(['localhost'])

        # cleanup
        shutil.rmtree(temp_dir)

        assert 'foo' not in play.vars, \
            "mixed scope vars_file loaded into play vars"
        assert 'foo' in play.playbook.VARS_CACHE['localhost'], \
            "differently scoped templated vars_files filename not loaded"
        assert play.playbook.VARS_CACHE['localhost']['foo'] == 'bar', \
            "foo is not bar"