#!/bin/bash

# Same as in pre-commit, improving error handling.
set -Eeuo pipefail
error_handler() {
    local exit_code=$?
    local line_number=$1
    echo "Error on line $line_number. Exit code: $exit_code"
    exit $exit_code
}
trap 'error_handler $LINENO' ERR

# Load the pre-commit script
. ./pre-commit-src/pre-commit-script.sh --load-only

score=0
total=0

mode="${1:-}"

# Function to check if a file exists
check_if_file_exists() {
    local file_path="$1"
    if [ -f "$file_path" ]; then
        return 0
    else
        return 1
    fi
}

# Error handling would stop execution if the function fails
# So here is a little work around
execute_function_and_capture_exit_code() {
    local -n exit_code_nameref=$1
    local func="$2"
    shift 2
    exit_code_nameref=0
    # If the function fails, change exit_code value to 1 (instead of getting error code trapped)
    "$func" "$@" || exit_code_nameref=1
    local catched_exit_code=$?
    if [ $exit_code_nameref -ne 1 ]; then
        exit_code_nameref=$catched_exit_code
    fi
}

update_results() {
    local condition="$1"
    local test_comment="$2"
    if eval $condition; then
        if [ "$mode" != '--no-verbose' ]; then
            echo -n 'Test passed: '
            echo $test_comment
        fi
        score=$((score+1))
    else
        echo -n 'Test failed: '
        echo $test_comment
    fi

    total=$((total+1))
}

print_running_function() {
    if [ "$mode" != '--no-verbose' ]; then
        echo -e "\nRunning $1..."
    fi
}
test_check_if_file_exists() {
    print_running_function "$FUNCNAME"
    
    local exit_code
    # Test with a non-existent file
    local non_existent_path='./not_existing_file.dummy'
    execute_function_and_capture_exit_code exit_code 'check_if_file_exists' "$non_existent_path"

    update_results "[ $exit_code -ne 0 ]" 'non-existent file'

    # Test with an existing file

    local existent_path=$(mktemp)
    touch "$existent_path"
    execute_function_and_capture_exit_code exit_code 'check_if_file_exists' "$existent_path"

    rm "$existent_path"
    update_results "[ $exit_code -eq 0 ]" 'existent file'
}

test_check_if_valid_json() {
    print_running_function "$FUNCNAME"

    local exit_code

    # Test with an invalid JSON file
    local invalid_json_file_content='{"invalid": ["json"}'
    local invalid_json_file_path=$(mktemp)
    echo "$invalid_json_file_content" > "$invalid_json_file_path"
    execute_function_and_capture_exit_code exit_code 'check_if_valid_json' "$invalid_json_file_path"

    rm "$invalid_json_file_path"
    update_results "[ $exit_code -ne 0 ]" 'invalid json'

    # Test with a valid JSON file
    local valid_json_file_content='{"key": "value"}'
    local valid_json_file_path=$(mktemp)
    echo "$valid_json_file_content" > "$valid_json_file_path"
    execute_function_and_capture_exit_code exit_code 'check_if_valid_json' "$valid_json_file_path"

    rm "$valid_json_file_path"
    update_results "[ $exit_code -eq 0 ]" 'valid json'
}

test_check_if_correct_domains_variables_json_structure() {
    print_running_function "$FUNCNAME"

    local exit_code

    # Test with an invalid structure
    local invalid_json_file_structure_content='{"key": "value"}'
    local invalid_json_file_structure_path=$(mktemp)
    echo "$invalid_json_file_structure_content" > "$invalid_json_file_structure_path"
    execute_function_and_capture_exit_code exit_code 'check_if_correct_domains_variables_json_structure' "$invalid_json_file_structure_content", "$invalid_json_file_structure_path"

    rm "$invalid_json_file_structure_path"
    update_results "[ $exit_code -ne 0 ]" 'invalid json structure'

    # Test with a valid structure
    local valid_json_file_structure_content='{"variable1": ["domain1", "domain2"], "variable2": ["domain3"]}'
    local valid_json_file_structure_path=$(mktemp)
    echo "$valid_json_file_structure_content" > "$valid_json_file_structure_path"
    execute_function_and_capture_exit_code exit_code 'check_if_correct_domains_variables_json_structure' "$valid_json_file_structure_content" "$valid_json_file_structure_path"

    rm "$valid_json_file_structure_path"
    update_results "[ $exit_code -eq 0 ]" 'valid json structure'
}

test_domains_variables_names_incl_duplicates() {
    print_running_function "$FUNCNAME"

    local exit_code

    # Test if duplicated variable names are collected
    local json_file_with_duplicates_content='{"variable1": ["domain1"], "variable1": ["domain2"]}'
    local domains_variables_with_duplicated
    execute_function_and_capture_exit_code exit_code 'get_domains_variables_names_incl_duplicates' "$json_file_with_duplicates_content" domains_variables_with_duplicated

    local expected_domains_variables_with_duplicates=$(echo -e 'variable1\nvariable1')
    update_results "[ \"$domains_variables_with_duplicated\" = \"$expected_domains_variables_with_duplicates\" ]" 'collecting variable names incl. duplicates'
}

test_check_if_duplicated_domains_variable_name_in_single_file() {
    print_running_function "$FUNCNAME"

    local exit_code

    # Test with duplicated variable names
    local json_file_with_duplicates_path=$(mktemp)
    local json_file_with_duplicates_content='{"variable1": ["domain1"], "variable1": ["domain2"]}'
    echo "$json_file_with_duplicates_content" > "$json_file_with_duplicates_path"
    
    local domains_variables_with_duplicated
    get_domains_variables_names_incl_duplicates "$json_file_with_duplicates_content" domains_variables_with_duplicated

    execute_function_and_capture_exit_code exit_code 'check_if_duplicated_domains_variable_name_in_single_file' "$json_file_with_duplicates_path" "$domains_variables_with_duplicated"

    rm "$json_file_with_duplicates_path"
    update_results "[ $exit_code -ne 0 ]" 'duplicated variable names'

    # Test with unique variable names
    local json_file_without_duplicates_path=$(mktemp)
    local json_file_without_duplicates_content='{"variable1": ["domain1"], "variable2": ["domain2"]}'
    echo "$json_file_without_duplicates_content" > "$json_file_without_duplicates_path"

    local domains_variables_without_duplicated
    get_domains_variables_names_incl_duplicates "$json_file_without_duplicates_content" domains_variables_without_duplicated

    execute_function_and_capture_exit_code exit_code 'check_if_duplicated_domains_variable_name_in_single_file' "$json_file_without_duplicates_path" "$domains_variables_without_duplicated"

    rm "$json_file_without_duplicates_path"
    update_results "[ $exit_code -eq 0 ]" 'unique variable names'
}

test_check_if_valid_domains_variable_name() {
    print_running_function "$FUNCNAME"

    local exit_code

    # Test with an invalid variable name
    local invalid_variable_name='invalid-name'
    execute_function_and_capture_exit_code exit_code 'check_if_valid_domains_variable_name' "$invalid_variable_name"

    update_results "[ $exit_code -ne 0 ]" 'invalid variable name'

    # Test with a valid variable name
    local valid_variable_name='valid_name'
    execute_function_and_capture_exit_code exit_code 'check_if_valid_domains_variable_name' "$valid_variable_name"

    update_results "[ $exit_code -eq 0 ]" 'valid variable name'
}

test_check_if_duplicated_domains() {
    print_running_function "$FUNCNAME"

    local exit_code

    # Test with duplicated domains
    local json_file_with_duplicated_domain_name='duplicated_domains.json'
    local variable_with_duplicated_domain_name='variable1'
    local duplicated_domains=$(echo -e 'domain1\ndomain1')
    execute_function_and_capture_exit_code exit_code 'check_if_duplicated_domains' "$json_file_with_duplicated_domain_name" "$variable_with_duplicated_domain_name" "$duplicated_domains"

    update_results "[ $exit_code -ne 0 ]" 'duplicated domains'

    # Test with unique domains
    local json_file_without_duplicated_domain_name='unique_domains.json'
    local variable_without_duplicated_domain_name='variable1'
    local unique_domains=$(echo -e 'domain1\ndomain2')
    execute_function_and_capture_exit_code exit_code 'check_if_duplicated_domains' "$json_file_without_duplicated_domain_name" "$variable_without_duplicated_domain_name" "$unique_domains"

    update_results "[ $exit_code -eq 0 ]" 'unique domains'
}

test_check_if_correct_domain() {
    print_running_function "$FUNCNAME"

    local exit_code

    # Test with an invalid domain
    local json_file_with_invalid_domain_name='invalid_domain.json'
    local variable_with_invalid_domain_name='variable1'
    local invalid_domain='invalid).com'
    execute_function_and_capture_exit_code exit_code 'check_if_correct_domain' "$json_file_with_invalid_domain_name" "$variable_with_invalid_domain_name" "$invalid_domain"

    update_results "[ $exit_code -ne 0 ]" 'invalid domain'

    # Test with a valid domain
    local json_file_with_valid_domain_name='valid_domain.json'
    local variable_with_valid_domain_name='variable1'
    local valid_domain='example.com'
    execute_function_and_capture_exit_code exit_code 'check_if_correct_domain' "$json_file_with_valid_domain_name" "$variable_with_valid_domain_name" "$valid_domain"

    update_results "[ $exit_code -eq 0 ]" 'valid domain'
}

test_check_if_correct_domains_variables() {
    print_running_function "$FUNCNAME"

    local exit_code

    # Test with an invalid domains variable
    local invalid_json_file_path=$(mktemp)
    local invalid_json_file_content='{"variable1": ["invalid).com"]}'
    echo "$invalid_json_file_content" > "$invalid_json_file_path"
    local domains_variables_with_duplicated
    get_domains_variables_names_incl_duplicates "$invalid_json_file_content" domains_variables_with_duplicated
    execute_function_and_capture_exit_code exit_code 'check_if_correct_domains_variables' "$invalid_json_file_path" "$domains_variables_with_duplicated" "$invalid_json_file_content"

    rm "$invalid_json_file_path"
    update_results "[ $exit_code -ne 0 ]" 'invalid domains variable'

    # Test with a valid domains variable
    local valid_json_file_path=$(mktemp)
    local valid_json_file_content='{"variable1": ["example.com"]}'
    echo "$valid_json_file_content" > "$valid_json_file_path"
    local domains_variables_without_duplicated
    get_domains_variables_names_incl_duplicates "$valid_json_file_content" domains_variables_without_duplicated
    execute_function_and_capture_exit_code exit_code 'check_if_correct_domains_variables' "$valid_json_file_path" "$domains_variables_without_duplicated" "$valid_json_file_content"

    rm "$valid_json_file_path"
    update_results "[ $exit_code -eq 0 ]" 'valid domains variable'
}

test_check_if_duplicated_domains_variable_name_between_files() {
    print_running_function "$FUNCNAME"

    local exit_code
    # Reseting global variables from pre-commit in case there was something
    all_domains_variables_in_json_files='[]'
    all_json_files_contents='{}'
    variables_in_json_files='{}'


    # Test with duplicated variable names between files
    local first_json_file_with_duplicated_path=$(mktemp)
    local second_json_file_with_duplicated_path=$(mktemp)
    local first_json_file_with_duplicated_content='{"variable1": ["domain1"]}'
    local second_json_file_with_duplicated_content='{"variable1": ["domain2"]}'
    local all_files_with_duplicated=$(echo -e "$first_json_file_with_duplicated_path\n$second_json_file_with_duplicated_path")
    echo "$first_json_file_with_duplicated_content" > "$first_json_file_with_duplicated_path"
    echo "$second_json_file_with_duplicated_content" > "$second_json_file_with_duplicated_path"

    local failed_with_duplicated=false
    for file in $all_files_with_duplicated; do
        local json_file_content=$(cat $file)
        local domains_variables_with_duplicated
        get_domains_variables_names_incl_duplicates "$json_file_content" domains_variables_with_duplicated
        execute_function_and_capture_exit_code exit_code 'check_if_duplicated_domains_variable_name_between_files' "$file" "$domains_variables_with_duplicated"
        update_domains_variables_data "$domains_variables_with_duplicated" "$file" "$json_file_content"
        if [ $exit_code -ne 0 ]; then
            failed_with_duplicated=true
            break
        fi
    done

    rm "$first_json_file_with_duplicated_path" "$second_json_file_with_duplicated_path"
    update_results "$failed_with_duplicated" 'duplicated variable names between files'

    all_domains_variables_in_json_files='[]'
    all_json_files_contents='{}'
    variables_in_json_files='{}'

    # Test with unique variable names between files
    local first_json_file_without_duplicated_path=$(mktemp)
    local second_json_file_without_duplicated_path=$(mktemp)
    local first_json_file_without_duplicated_content='{"variable1": ["domain1"]}'
    local second_json_file_without_duplicated_content='{"variable2": ["domain2"]}'
    local all_files_without_duplicated=$(echo -e "$first_json_file_without_duplicated_path\n$second_json_file_without_duplicated_path")
    echo "$first_json_file_without_duplicated_content" > "$first_json_file_without_duplicated_path"
    echo "$second_json_file_without_duplicated_content" > "$second_json_file_without_duplicated_path"

    local failed_without_duplicated=false
    for file in $all_files_without_duplicated; do
        local json_file_content=$(cat $file)
        local domains_variables_without_duplicated
        get_domains_variables_names_incl_duplicates "$json_file_content" domains_variables_without_duplicated

        execute_function_and_capture_exit_code exit_code 'check_if_duplicated_domains_variable_name_between_files' "$file" "$domains_variables_without_duplicated"
        update_domains_variables_data "$domains_variables_without_duplicated" "$file" "$json_file_content"
        if [ $exit_code -ne 0 ]; then
            failed_without_duplicated=true
            break
        fi
    done

    rm "$first_json_file_without_duplicated_path" "$second_json_file_without_duplicated_path"
    update_results "! $failed_without_duplicated" 'unique variable names between files'

    all_domains_variables_in_json_files='[]'
    all_json_files_contents='{}'
    variables_in_json_files='{}'
}

test_find_find_domain_variables_syntax_in_filterlist() {
    print_running_function "$FUNCNAME"

    local exit_code

    # Test with a filterlist containing domain variables
    local filterlist_with_domain_variables_content='%<{variable1}>%##withDomainVariable'
    local filterlist_with_domain_variables_path=$(mktemp)
    echo "$filterlist_with_domain_variables_content" > "$filterlist_with_domain_variables_path"
    local all_lines_with_domain_variables_in_filterlist
    execute_function_and_capture_exit_code exit_code 'find_domain_variables_syntax_in_filterlist' "$filterlist_with_domain_variables_content" "$filterlist_with_domain_variables_path" all_lines_with_domain_variables_in_filterlist
    
    rm $filterlist_with_domain_variables_path
    update_results "[ $exit_code -eq 0 ] && [ -n \"$all_lines_with_domain_variables_in_filterlist\" ]" 'filterlist containing domain variables'

    # Test with a filterlist without domain variables
    local filterlist_without_domain_variables_content='##noDomainVariable'
    local filterlist_without_domain_variables_path=$(mktemp)
    echo "$filterlist_without_domain_variables_content" > "$filterlist_without_domain_variables_path"
    execute_function_and_capture_exit_code exit_code 'find_domain_variables_syntax_in_filterlist' "$filterlist_without_domain_variables_content" "$filterlist_without_domain_variables_path" all_lines_with_domain_variables_in_filterlist

    rm $filterlist_without_domain_variables_path
    update_results "[ $exit_code -eq 0 ] && [ -z \"$all_lines_with_domain_variables_in_filterlist\" ]" 'filterlist without domain variables'
}

test_process_filters() {
    print_running_function "$FUNCNAME"

    local exit_code

    # Test with valid filters
    local valid_lines_with_domain_variables_in_filterlist=$(echo -e '%<{variable1}>%#@#filter\n||valid$domain=%<{variable2}>%\ndomain1.com,%<{variable3}>%##withVariable')
    local domains_variables_collected_from_filterlist
    execute_function_and_capture_exit_code exit_code 'process_filters' "$valid_lines_with_domain_variables_in_filterlist" domains_variables_collected_from_filterlist
    local expected_lines_with_domain_variables_in_filterlist='[ "variable1", "variable2", "variable3" ]'

    update_results "[ $exit_code -eq 0 ] && [ \"$expected_lines_with_domain_variables_in_filterlist\" = \"$domains_variables_collected_from_filterlist\" ]" 'valid filters'

    # Test with invalid filters
    local invalid_lines_with_domain_variables_in_filterlist=$(echo -e '%<{variable1}>%#@#filter\n||valid$domain=%<{variable2}>%\ndomain1.com##noVariable')
    execute_function_and_capture_exit_code exit_code 'process_filters' "$invalid_lines_with_domain_variables_in_filterlist" domains_variables_collected_from_filterlist
    
    update_results "[ $exit_code -ne 0 ]" 'invalid filters'
}

test_update_matches_and_variables() {
    print_running_function "$FUNCNAME"

    local exit_code

    # Test with valid matches
    all_domain_variables_matches_in_filterlists='["variable0", "variable1"]'
    local domains_variables_collected_from_filterlist='["variable2"]'
    execute_function_and_capture_exit_code exit_code 'update_matches_and_variables' "$domains_variables_collected_from_filterlist" 'filterlist.txt'

    local expected_all_domain_cariables_matches_in_filterlists='[ "variable0", "variable1", "variable2" ]'
    update_results "[ $exit_code -eq 0 ] && [ \"$expected_all_domain_cariables_matches_in_filterlists\" = \"$all_domain_variables_matches_in_filterlists\" ]" 'valid matches'

    all_domain_variables_matches_in_filterlists='[]'
}

test_extract_domains_variables_in_included_filterlists() {
    print_running_function "$FUNCNAME"

    local exit_code

    # Test with valid included filterlists
    templates_content='{"template.txt": {"include": ["filterlist1.txt", "filterlist2.txt", "filterlist3.txt"]}}'
    variables_in_included_filterlists='{"filterlist1.txt": ["variable1"], "filterlist2.txt": ["variable2", "variable3"], "filterlist3.txt": [], "filterlist4.txt": ["variable4"]}'
    local domains_variables_in_included_filterlists
    execute_function_and_capture_exit_code exit_code 'extract_domains_variables_in_included_filterlists' 'template.txt' domains_variables_in_included_filterlists

    local expected_domains_variables_in_included_filterlists="variable1 variable2 variable3"
    update_results "[ $exit_code -eq 0 ] && [ \"$expected_domains_variables_in_included_filterlists\" = \"$(echo ${domains_variables_in_included_filterlists[@]})\" ]" 'valid extraction of variables from filterlist included files'

    templates_content='{}'
}

test_extract_domains_variables_in_included_json_files() {
    print_running_function "$FUNCNAME"

    local exit_code

    # Test with valid included JSON files
    templates_content='{"template.txt": {"domainsVariables": ["file1.json", "file2.json", "file3.json"]}}'
    variables_in_json_files='{"file1.json": ["variable1"], "file2.json": ["variable2", "variable3"], "file3.json": [], "file4.json": ["variable4"]}'
    local domains_variables_in_included_json_files
    execute_function_and_capture_exit_code exit_code 'extract_domains_variables_in_included_json_files' 'template.txt' domains_variables_in_included_json_files

    local expected_domains_variables_in_included_json_files="variable1 variable2 variable3"
    update_results "[ $exit_code -eq 0 ] && [ \"$expected_domains_variables_in_included_json_files\" = \"$(echo ${domains_variables_in_included_json_files[@]})\" ]" 'valid extraction of domains variables from filterlist json file'

    templates_content='{}'
}

test_check_domain_variables_in_filterlists() {
    print_running_function "$FUNCNAME"

    local exit_code

    # Test with matching domain variables
    local matching_template_name='matching_variables.txt'
    templates_content='{"matching_variables.txt": {"include": ["filterlist1.txt", "filterlist2.txt", "filterlist3.txt"], "domainsVariables": ["file1.json", "file2.json", "file3.json"]}}'
    local matching_variables_in_filterlists='variable1 variable2 variable3'
    local matching_variables_in_json_files='variable1 variable2 variable3'
    execute_function_and_capture_exit_code exit_code 'check_domain_variables_in_filterlists' "$matching_template_name" "$matching_variables_in_filterlists" "$matching_variables_in_json_files"

    update_results "[ $exit_code -eq 0 ]" 'matching domain variables between included files and json files'

    # Test with non-matching domain variables
    local non_matching_template_name='non-matching_variables.txt'
    templates_content='{"non-matching_variables.txt": {"include": ["filterlist1.txt", "filterlist2.txt", "filterlist3.txt"], "domainsVariables": ["file1.json", "file2.json", "file3.json"]}}'
    local non_matching_variables_in_filterlists='variable1 variable2 variable3'
    local non_matching_variables_in_json_files='variable1 variable2'
    execute_function_and_capture_exit_code exit_code 'check_domain_variables_in_filterlists' "$non_matching_template_name" "$non_matching_variables_in_filterlists" "$non_matching_variables_in_json_files"

    update_results "[ $exit_code -ne 0 ]" 'non-matching domain variables between included files and json files'

    templates_content='{}'
}

test_check_if_domains_variables_are_identical_in_lists_and_jsons() {
    print_running_function "$FUNCNAME"

    local exit_code

    # Test with identical domain variables
    all_domains_variables_in_json_files='["variable1", "variable2"]'
    all_domain_variables_matches_in_filterlists='["variable1", "variable2"]'
    execute_function_and_capture_exit_code exit_code 'check_if_domains_variables_are_identical_in_lists_and_jsons'

    update_results "[ $exit_code -eq 0 ]" 'identical domain variables'

    # Test with non-identical domain variables
    all_domains_variables_in_json_files='["variable1", "variable2"]'
    all_domain_variables_matches_in_filterlists='["variable1", "variable3"]'
    execute_function_and_capture_exit_code exit_code 'check_if_domains_variables_are_identical_in_lists_and_jsons'

    update_results "[ $exit_code -ne 0 ]" 'non-identical domain variables'

    all_domains_variables_in_json_files='[]'
}

test_check_if_file_exists
test_check_if_valid_json
test_check_if_correct_domains_variables_json_structure
test_domains_variables_names_incl_duplicates
test_check_if_duplicated_domains_variable_name_in_single_file
test_check_if_valid_domains_variable_name
test_check_if_duplicated_domains
test_check_if_correct_domain
test_check_if_correct_domains_variables
test_check_if_duplicated_domains_variable_name_between_files
test_find_find_domain_variables_syntax_in_filterlist
test_process_filters
test_update_matches_and_variables
test_extract_domains_variables_in_included_filterlists
test_extract_domains_variables_in_included_json_files
test_check_domain_variables_in_filterlists
test_check_if_domains_variables_are_identical_in_lists_and_jsons

# Remove aliases created in pre-commit-script.sh
unalias jq
unalias grep
unalias git

echo -e '\n-------------------------------------------\n'
if [ $score -eq $total ]; then
    if [ "$mode" != '--no-verbose' ]; then
        echo 'All tests from pre-commit-tests.sh passed.'
    fi
    exit 0
else
    if [ "$mode" != '--no-verbose' ]; then
        echo "$((total-score)) tests from pre-commit-tests.sh failed out of $total in total."
    fi
    exit 1
fi
