# -*- encoding: UTF8 -*-
# Test harness for CheckFolderStructure.py

from __future__ import print_function

import sys
import unittest
import os
import re

import P4
from p4testutils import TestCase, P4Server, localDirectory, create_file, append_to_file

python3 = sys.version_info[0] >= 3
if python3:
    from unittest.mock import Mock
else:
    from mock import Mock

parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, parent_dir)
from CheckFolderStructure import CheckFolderStructure, NewDirFinder
from WorkflowTriggers import GroupMemberChecker

os.environ["LOGS"] = "."
LOGGER_NAME = "TestCheckFolderStructure"
LOG_FILE = "log-TestCheckFolderStructure.log"


class TestCheckFolderStructure(TestCase):
    def __init__(self, methodName='runTest'):
        super(TestCheckFolderStructure, self).__init__(LOGGER_NAME, LOG_FILE, methodName=methodName)

    def setUp(self):
        self.server = P4Server()
        trigpath = os.path.join(parent_dir, "CheckFolderStructure.py")
        self.config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "~test_config.yaml")
        p4 = self.server.p4
        self.p4 = p4
        p4.logger = self.logger
        depot = p4.fetch_depot("streams")
        depot["Type"] = "stream"
        p4.save_depot(depot)
        # This works if no spaces in server root pathname!
        port = p4.port.replace('"', '')
        self.logger.debug("port: |%s|" % port)
        triggers = p4.fetch_triggers()
        triggers['Triggers'] = ['check-folder-structure change-submit //streams/... " python {} -p %quote%{}%quote% '
                                '-u {} -c {} %change% "'.format(trigpath, port, p4.user, self.config_path),
                                ]
        self.logger.debug(triggers)
        p4.save_triggers(triggers)
        # Reconnect to pick up changes
        p4.disconnect()
        p4.connect()
        stream = p4.fetch_stream("-tmainline", "//streams/main")
        p4.save_stream(stream)
        client = p4.fetch_client()
        client['Stream'] = "//streams/main"
        p4.save_client(client)

    def tearDown(self):
        pass

    def testCheckNewDirs(self):
        """unit testing with mock framework"""

        mock_p4 = Mock()
        mock_p4.run_dirs.return_value = []

        df = NewDirFinder(mock_p4)
        level = 0

        self.assertFalse(df.findNewDir("//depot/stream/fred.txt", level))
        self.assertFalse(df.findNewDir("//depot/stream/dir/fred.txt", level))
        self.assertFalse(df.findNewDir("//depot/stream/dir/subdir/fred.txt", level))

        mock_p4.run_dirs.return_value = []
        df = NewDirFinder(mock_p4)

        level = 1
        self.assertFalse(df.findNewDir("//depot/stream/fred.txt", level))

        self.assertEqual("//depot/stream/dir", df.findNewDir("//depot/stream/dir/fred.txt", level))

        mock_p4.run_dirs.return_value = [{'dir': "//depot/stream/dir"}]
        call_count = mock_p4.run_dirs.call_count
        df = NewDirFinder(mock_p4)
        newDir = df.findNewDir("//depot/stream/dir/fred.txt", level)
        self.assertFalse(newDir)
        call_count += 1
        self.assertEqual(call_count, mock_p4.run_dirs.call_count)

        # Test caching - so don't expect call count to be incremented even if we call it
        # multiple times
        mock_p4.run_dirs.return_value = [{'dir': "//depot/stream/dir"}]
        call_count = mock_p4.run_dirs.call_count
        df = NewDirFinder(mock_p4)
        self.assertFalse(df.findNewDir("//depot/stream/dir/fred.txt", level))
        self.assertFalse(df.findNewDir("//depot/stream/dir/fred2.txt", level))
        self.assertFalse(df.findNewDir("//depot/stream/dir/fred3.txt", level))
        call_count += 1
        self.assertEqual(call_count, mock_p4.run_dirs.call_count)

        # Test multiple dirs under that level
        mock_p4.run_dirs.return_value = [{'dir': "//depot/stream/dir1"},
                                         {'dir': "//depot/stream/dir2"}]
        call_count = mock_p4.run_dirs.call_count
        df = NewDirFinder(mock_p4)
        self.assertFalse(df.findNewDir("//depot/stream/dir1/fred.txt", level))
        self.assertFalse(df.findNewDir("//depot/stream/dir2/fred2.txt", level))
        self.assertFalse(df.findNewDir("//depot/stream/dir2/fred3.txt", level))
        self.assertEqual("//depot/stream/dir3", df.findNewDir("//depot/stream/dir3/fred4.txt", level))
        call_count += 1
        self.assertEqual(call_count, mock_p4.run_dirs.call_count)

        # Test multiple depots - so multiple calls to dirs
        mock_p4.run_dirs.side_effect = [[{'dir': "//depotA/streamX/dir1"}],
                                        [{'dir': "//depotB/streamY/dir2"}]]
        call_count = mock_p4.run_dirs.call_count
        df = NewDirFinder(mock_p4)
        self.assertFalse(df.findNewDir("//depotA/streamX/dir1/fred.txt", level))
        self.assertFalse(df.findNewDir("//depotB/streamY/dir2/fred2.txt", level))
        self.assertEqual("//depotB/streamY/dir3", df.findNewDir("//depotB/streamY/dir3/fred4.txt", level))
        call_count += 2
        self.assertEqual(call_count, mock_p4.run_dirs.call_count)
        mock_p4.run_dirs.side_effect = None

        # Now test level = 2
        # Test multiple dirs under that level
        level = 2
        call_count = mock_p4.run_dirs.call_count
        mock_p4.run_dirs.side_effect = [[{'dir': "//depot/stream/dir1"}],
                                        [{'dir': "//depot/stream/dir1/dir2"}]]
        df = NewDirFinder(mock_p4)
        self.assertFalse(df.findNewDir("//depot/stream/dir1/fred.txt", level))
        self.assertFalse(df.findNewDir("//depot/stream/dir1/dir2/fred2.txt", level))
        self.assertEqual("//depot/stream/dir3", df.findNewDir("//depot/stream/dir3/fred4.txt", level))
        self.assertEqual("//depot/stream/dir1/dir3", df.findNewDir("//depot/stream/dir1/dir3/fred4.txt", level))
        call_count += 2
        self.assertEqual(call_count, mock_p4.run_dirs.call_count)


    def testGroupMemberChecker(self):
        """test group membership"""

        mock_p4 = Mock()
        mock_p4.run_groups.return_value = [
            {'user': 'user1', 'group': 'G1', 'isSubGroup': '0', 'isOwner': '0', 'isUser': '1'},
            {'user': 'user2', 'group': 'G1', 'isSubGroup': '0', 'isOwner': '0', 'isUser': '1'},
            {'user': 'user3', 'group': 'G3', 'isSubGroup': '0', 'isOwner': '1', 'isUser': '0'},
            # Next record means G1 is a subgroup of G2
            {'user': 'G1', 'group': 'G2', 'isSubGroup': '1', 'isOwner': '0', 'isUser': '0'},
            {'user': 'G2', 'group': 'G3', 'isSubGroup': '1', 'isOwner': '0', 'isUser': '0'}]

        gchk = GroupMemberChecker(mock_p4)

        exceptions_list = ['user1']
        self.assertTrue(gchk.IsMember('user1', exceptions_list))
        self.assertFalse(gchk.IsMember('user2', exceptions_list))
        exceptions_list = ['user1', 'user2']
        self.assertTrue(gchk.IsMember('user2', exceptions_list))

        exceptions_list = ['G1']
        self.assertTrue(gchk.IsMember('user1', exceptions_list))
        self.assertTrue(gchk.IsMember('user2', exceptions_list))

        exceptions_list = ['G2']
        self.assertTrue(gchk.IsMember('user1', exceptions_list))
        self.assertTrue(gchk.IsMember('user2', exceptions_list))
        self.assertFalse(gchk.IsMember('user3', exceptions_list))

        exceptions_list = ['G3']
        # G3 has subgroup G2 which has subgroup G1
        self.assertTrue(gchk.IsMember('user1', exceptions_list))
        self.assertTrue(gchk.IsMember('user2', exceptions_list))
        self.assertTrue(gchk.IsMember('user3', exceptions_list))
        self.assertFalse(gchk.IsMember('user4', exceptions_list))


    def testCheckFolderStructure(self):
        """check that it works when called as a trigger"""

        p4 = self.p4

        # Create a directory

        dir1 = localDirectory(self.server.client_root, "dir1")
        file1 = os.path.join(dir1, "file1")
        create_file(file1, "Some content")

        # Allow new dirs at all levels
        with open(self.config_path, "w") as f:
            f.write("""
msg_new_folder_not_allowed:
  - ""
  - "You are not allowed to create new folders at this level in the stream."
  - "Please add your files/folders to an existing folder at this level."

projects:
  - name: ProjectA
    new_folder_allowed_level: 0
    depot_paths:
      - //streams/...
""")

        p4.run('add', file1)
        result = p4.run('submit', '-d', 'file1 added')
        self.assertTrue('submittedChange' in result[-1])

        # Now block level 1
        with open(self.config_path, "w") as f:
            f.write("""
msg_new_folder_not_allowed:
  - ""
  - "You are not allowed to create new folders at this level in the stream."
  - "Please add your files/folders to an existing folder at this level."

projects:
  - name: ProjectA
    new_folder_allowed_level: 1
    depot_paths:
      - //streams/...
""")

        dir2 = localDirectory(self.server.client_root, "dir2")
        file2 = os.path.join(dir2, "file2")
        create_file(file2, "Some content")
        p4.run('add', file2)

        try:
            p4.run('submit', '-d', 'file2 added')
            self.assertTrue(False, "Expected exception not found")
        except P4.P4Exception as e:
            self.assertRegex(str(e), r"You are not allowed to create new folders")

        p4.run('revert', file2)

        # But creating file in existing dir is OK
        file3 = os.path.join(dir1, "file3")
        create_file(file3, "Some content")
        p4.run('add', file3)
        result = p4.run('submit', '-d', 'file3 added')
        self.assertTrue('submittedChange' in result[-1])

        # Moving a file also OK in existing dir but not a new dir
        file4 = os.path.join(dir1, "file4")
        p4.run('edit', file1)
        p4.run('move', file1, file4)
        result = p4.run('submit', '-d', 'file1 moved')
        self.assertTrue('submittedChange' in result[-1])

        file5 = os.path.join(dir2, "file5")
        p4.run('edit', file3)
        p4.run('move', file3, file5)
        try:
            p4.run('submit', '-d', 'file3 renamed')
            self.assertTrue(False, "Expected exception not found")
        except P4.P4Exception as e:
            self.assertRegex(str(e), r"You are not allowed to create new folders")


if __name__ == '__main__':
    unittest.main()
