#!/usr/bin/env python3
# [Page] Functional Test Template
# Authors:
#     Dakota Cookenmaster
#     Jesse Hines

from trello import TrelloClient
import re
import textwrap
import json
import os
import subprocess

TEST_FOLDER = os.path.dirname(os.sys.argv[0])

if os.path.exists(f'{TEST_FOLDER}/trello-api.json'):
    with open(f'{TEST_FOLDER}/trello-api.json') as file:
        params = json.load(file)
        client = TrelloClient(
            api_key=params['api_key'],
            api_secret=params['api_secret'],
            token=params['token'],
        )
else:
    print(f"""Add {TEST_FOLDER}/trello-api.json and run again
{{
  "api_key": "your key here",
  "api_secret": "your secret here",
  "token": "your token here"
}}
""")
    exit(1)

FIXTURE_DATA_PATH = f'{TEST_FOLDER}/fixtures/data'
MODEL_FOLDER = f'{TEST_FOLDER}/../models'
PHP_FIXTURE_JSON_CONVERTER = f'{TEST_FOLDER}/fixture_to_json.php'
SHARE_HIM_BOARD = client.get_board('5f03979153257d3ed6e7b85e')
cards = SHARE_HIM_BOARD.get_cards()


def capitalize_first(word):
    return word[0].upper() + word[1::]


def decapitalize_first(word):
    return word[0].lower() + word[1::]


def camel_to_snake(camel_string):
    # https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case
    return re.sub(r'(?<!^)(?=[A-Z])', '_', camel_string).lower()


def snake_to_camel(snake_string):
    # https://www.w3resource.com/python-exercises/re/python-re-exercise-37.php
    return ''.join((capitalize_first(word) for word in re.split('[_-]', snake_string)))


def reindent(content, level):
    return textwrap.indent(textwrap.dedent(content).strip(), " " * 4 * level)


def fixture_data_dict(model_name):
    if not os.path.exists(f"{MODEL_FOLDER}/{model_name}.php"):
        return None
    result = subprocess.run(['php', PHP_FIXTURE_JSON_CONVERTER,
                             f'{FIXTURE_DATA_PATH}/{get_table_name_from_model(f"{MODEL_FOLDER}/{model_name}.php")}.php'],
                            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    data = json.loads(result.stdout)
    return data


def get_table_name_from_model(model_path):
    model_php = open(model_path).read()
    table_search = re.search(r'{{%([^}]*)}}', model_php)
    if table_search:
        return table_search.group(1)
    else:
        return ''


def template_fixture_function(models):
    fixtures = "\n".join(fr"            app\tests\fixtures\{model}Fixture::class," for model in models)
    return fr'''
    public function _fixtures()
    {{
        return [
{fixtures}
        ];
    }}'''


# Function templates
templates = {
    'seeButton': lambda params: fr"""$I->see("{params["param"]}", "a, button");""",
    'seeLink': lambda params: fr"""$I->seeElement("a[href$='{params["param"]}']");""",
    'seeTitle': lambda params: fr"""$I->seeInTitle("{params["param"]}");""",
    'seeHeader': lambda params: fr"""$I->see("{params["param"]}", "h1, h2, h3, h4, h5, h6");""",
}


def default_template(params):
    return fr"""// TODO: {params['tag']};"""


# Creates a test function containing tests for a single route
def create_test_function(route, tests):
    route = route.replace("<#>", "1")  # fill get parameters, assume 1.
    controller, page = route.strip("/").split("/", 1)
    page = snake_to_camel(page.split("?")[0])

    tests = [reindent(test, 2) for test in tests]  # Re-indent the tests
    tests = "\n\n".join(tests)

    return fr'''
    public function test{page}(FunctionalTester $I)
    {{
        $I->login("demo");
        $I->amOnPage('{route}');

{tests}
    }}
    '''


tests = {};  # dict of controller names to dict containing routes to tests.
for card in cards:
    if ("[Page]" in card.name):
        route = card.name.replace("[Page]", "").strip()
        controller, page = route.strip("/").split("/", 1)
        controller = snake_to_camel(controller)

        tests.setdefault(controller, {})
        tests[controller].setdefault(route, [])

        for checklist in card.checklists:
            if checklist.name == "To Do":
                for item in checklist.items:
                    print(f"Creating test for: {item['name']}")
                    tag = re.search("\[(.*?)\]", item['name'])
                    if tag:
                        tag = tag.group(1)
                        tagName, _, tagParam = re.search("(\w*)(\((.*?)\))?", tag).groups()

                        template = templates.get(tagName, default_template)
                        tests[controller][route].append(
                            template({"route": route, "controller": controller, "tag": tagName, "param": tagParam}))
                    else:  # no tag found, just add comment
                        tests[controller][route].append(f"// TODO: {item['name']}")

for controller, controllerTests in tests.items():
    print(f"Writing {controller}Cest.php")

    fixtures = ["User"]
    if fixture_data_dict(controller):
        fixtures.append(controller)

    testFunctions = [
        template_fixture_function(fixtures),
        r"""
        public function _before(FunctionalTester $I)
        {
        }
        """,
    ]

    for route, routeTests in controllerTests.items():
        testFunctions.append( create_test_function(route, routeTests) )

    testFunctions = "\n\n".join(map(lambda e: reindent(e, 1), testFunctions))

    with open(f"{TEST_FOLDER}/functional/{controller}Cest.php", "w+") as file:
        file.write(fr'''<?php
use yii\helpers\Url;
use \Facebook\WebDriver\WebDriverKeys;
use app\models\{controller};

class {controller}Cest
{{
{testFunctions}
}}
'''
                   )
