// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Letter Combinations Solution
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "LetterCombinations.h"
#include <iostream>
#include <cassert>
#include <algorithm>
#include <set>

/**
 * @class LetterCombinationsTest
 * @brief Comprehensive test suite for LetterCombinations class
 */
class LetterCombinationsTest {
public:
    /**
     * @brief Run all test cases
     */
    void RunAllTests() {
        std::cout << "=== Letter Combinations Test Suite ===" << std::endl;
        
        TestEmptyInput();
        TestSingleDigit();
        TestTwoDigits();
        TestThreeDigits();
        TestFourDigits();
        TestInvalidInput();
        TestDigitMapping();
        TestBothSolutions();
        TestEdgeCases();
        
        std::cout << "\n✅ All tests passed!" << std::endl;
    }

private:
    LetterCombinations mSolution;
    int mTestCount = 0;
    
    /**
     * @brief Helper to verify test results
     */
    void AssertEqual(const std::vector<std::string>& actual,
                    const std::vector<std::string>& expected,
                    const std::string& test_name) {
        ++mTestCount;
        
        // Convert to sets for order-independent comparison
        std::set<std::string> actual_set(actual.begin(), actual.end());
        std::set<std::string> expected_set(expected.begin(), expected.end());
        
        if (actual_set == expected_set) {
            std::cout << "✓ Test " << mTestCount << " (" << test_name << ") passed" << std::endl;
        } else {
            std::cout << "✗ Test " << mTestCount << " (" << test_name << ") failed" << std::endl;
            std::cout << "  Expected: ";
            PrintVector(expected);
            std::cout << "  Actual: ";
            PrintVector(actual);
            assert(false);
        }
    }
    
    /**
     * @brief Helper to print vector contents
     */
    void PrintVector(const std::vector<std::string>& vec) {
        std::cout << "[";
        for (size_t i = 0; i < vec.size(); ++i) {
            std::cout << "\"" << vec[i] << "\"";
            if (i < vec.size() - 1) std::cout << ", ";
        }
        std::cout << "]" << std::endl;
    }
    
    /**
     * @brief Test empty input
     */
    void TestEmptyInput() {
        auto result = mSolution.GetCombinations("");
        std::vector<std::string> expected = {};
        AssertEqual(result, expected, "Empty Input");
    }
    
    /**
     * @brief Test single digit input
     */
    void TestSingleDigit() {
        // Test digit 2
        auto result2 = mSolution.GetCombinations("2");
        std::vector<std::string> expected2 = {"a", "b", "c"};
        AssertEqual(result2, expected2, "Single Digit 2");
        
        // Test digit 7 (has 4 letters)
        auto result7 = mSolution.GetCombinations("7");
        std::vector<std::string> expected7 = {"p", "q", "r", "s"};
        AssertEqual(result7, expected7, "Single Digit 7");
        
        // Test digit 9
        auto result9 = mSolution.GetCombinations("9");
        std::vector<std::string> expected9 = {"w", "x", "y", "z"};
        AssertEqual(result9, expected9, "Single Digit 9");
    }
    
    /**
     * @brief Test two digits input
     */
    void TestTwoDigits() {
        auto result = mSolution.GetCombinations("23");
        std::vector<std::string> expected = {
            "ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"
        };
        AssertEqual(result, expected, "Two Digits 23");
    }
    
    /**
     * @brief Test three digits input
     */
    void TestThreeDigits() {
        auto result = mSolution.GetCombinations("234");
        // Should have 3 * 3 * 3 = 27 combinations
        assert(result.size() == 27);
        
        // Check a few specific combinations
        std::set<std::string> result_set(result.begin(), result.end());
        assert(result_set.count("adg") == 1);
        assert(result_set.count("beh") == 1);
        assert(result_set.count("cfi") == 1);
        
        std::cout << "✓ Test " << ++mTestCount << " (Three Digits 234) passed" << std::endl;
    }
    
    /**
     * @brief Test four digits input
     */
    void TestFourDigits() {
        auto result = mSolution.GetCombinations("2345");
        // Should have 3 * 3 * 3 * 3 = 81 combinations
        assert(result.size() == 81);
        
        // Check a few specific combinations
        std::set<std::string> result_set(result.begin(), result.end());
        assert(result_set.count("adgj") == 1);
        assert(result_set.count("behk") == 1);
        assert(result_set.count("cfil") == 1);
        
        std::cout << "✓ Test " << ++mTestCount << " (Four Digits 2345) passed" << std::endl;
    }
    
    /**
     * @brief Test invalid input
     */
    void TestInvalidInput() {
        // Test with digit 0
        auto result0 = mSolution.GetCombinations("0");
        std::vector<std::string> expected0 = {};
        AssertEqual(result0, expected0, "Invalid Digit 0");
        
        // Test with digit 1
        auto result1 = mSolution.GetCombinations("1");
        std::vector<std::string> expected1 = {};
        AssertEqual(result1, expected1, "Invalid Digit 1");
        
        // Test with mixed valid/invalid
        auto result_mixed = mSolution.GetCombinations("21");
        std::vector<std::string> expected_mixed = {};
        AssertEqual(result_mixed, expected_mixed, "Mixed Valid/Invalid");
    }
    
    /**
     * @brief Test digit mapping functionality
     */
    void TestDigitMapping() {
        assert(mSolution.GetLettersForDigit(2) == "abc");
        assert(mSolution.GetLettersForDigit(3) == "def");
        assert(mSolution.GetLettersForDigit(4) == "ghi");
        assert(mSolution.GetLettersForDigit(5) == "jkl");
        assert(mSolution.GetLettersForDigit(6) == "mno");
        assert(mSolution.GetLettersForDigit(7) == "pqrs");
        assert(mSolution.GetLettersForDigit(8) == "tuv");
        assert(mSolution.GetLettersForDigit(9) == "wxyz");
        assert(mSolution.GetLettersForDigit(0) == "");
        assert(mSolution.GetLettersForDigit(1) == "");
        
        std::cout << "✓ Test " << ++mTestCount << " (Digit Mapping) passed" << std::endl;
    }
    
    /**
     * @brief Test both recursive and iterative solutions produce same results
     */
    void TestBothSolutions() {
        std::vector<std::string> test_cases = {"2", "23", "234", "789", "2345"};
        
        for (const std::string& test_case : test_cases) {
            auto recursive_result = mSolution.GetCombinations(test_case);
            auto iterative_result = mSolution.GetCombinationsIterative(test_case);
            
            // Convert to sets for order-independent comparison
            std::set<std::string> recursive_set(recursive_result.begin(), recursive_result.end());
            std::set<std::string> iterative_set(iterative_result.begin(), iterative_result.end());
            
            assert(recursive_set == iterative_set);
        }
        
        std::cout << "✓ Test " << ++mTestCount << " (Both Solutions Match) passed" << std::endl;
    }
    
    /**
     * @brief Test edge cases
     */
    void TestEdgeCases() {
        // Test with digits that have 4 letters (7, 9)
        auto result79 = mSolution.GetCombinations("79");
        assert(result79.size() == 4 * 4); // 16 combinations
        
        // Test longest reasonable input
        auto result_long = mSolution.GetCombinations("23456789");
        // Should have 3*3*3*3*3*4*3*4 = 11664 combinations
        // Calculation: 2(3) * 3(3) * 4(3) * 5(3) * 6(3) * 7(4) * 8(3) * 9(4) = 3*3*3*3*3*4*3*4 = 11664
        assert(result_long.size() == 11664);
        
        std::cout << "✓ Test " << ++mTestCount << " (Edge Cases) passed" << std::endl;
    }
};

/**
 * @brief Main function to run tests
 */
int main() {
    LetterCombinationsTest test_suite;
    test_suite.RunAllTests();
    
    return 0;
}