"""
Test External Memory with 3 sequential DP (Dynamic Programming) tasks.

Each task builds on knowledge that could be learned from previous failures,
demonstrating cross-task learning via external memory.
"""
import sys
sys.path.insert(0, '.')

import os
import shutil
from dotenv import load_dotenv

load_dotenv()

from gcri.config import scope as gcri_scope
from gcri.graphs.gcri_unit import GCRI
from gcri.memory.external_memory import ExternalMemory

# DP problems that share common patterns and edge cases
DP_TASKS = [
    # Task 1: Classic DP with memoization
    {
        'name': 'Longest Increasing Subsequence',
        'description': """
Task: Implement Longest Increasing Subsequence (LIS)

Given an integer array nums, return the length of the longest strictly increasing subsequence.

A subsequence is derived from an array by deleting some or no elements without changing 
the order of the remaining elements.

Example:
- Input: nums = [10,9,2,5,3,7,101,18]
- Output: 4
- Explanation: [2,3,7,101] or [2,5,7,101]

Requirements:
1. O(n log n) time complexity using binary search + DP
2. Handle edge cases: empty array, single element, all same values, strictly decreasing
3. Write comprehensive tests

Write to 'lis.py' with a Solution class containing lengthOfLIS method.
"""
    },
    # Task 2: 2D DP - should benefit from LIS learnings
    {
        'name': 'Longest Common Subsequence',
        'description': """
Task: Implement Longest Common Subsequence (LCS)

Given two strings text1 and text2, return the length of their longest common subsequence.
If there is no common subsequence, return 0.

A subsequence is derived from an array by deleting some or no elements without changing 
the order of the remaining elements.

Example:
- Input: text1 = "abcde", text2 = "ace"
- Output: 3
- Explanation: "ace" is the LCS

Requirements:
1. Use 2D DP with O(m*n) time complexity
2. Handle edge cases: empty strings, single char, identical strings, no common chars
3. Write comprehensive tests

Write to 'lcs.py' with a Solution class containing longestCommonSubsequence method.
"""
    },
    # Task 3: Advanced DP - should benefit from both LIS and LCS learnings
    {
        'name': 'Edit Distance',
        'description': """
Task: Implement Edit Distance (Levenshtein Distance)

Given two strings word1 and word2, return the minimum number of operations 
required to convert word1 to word2.

Allowed operations:
- Insert a character
- Delete a character
- Replace a character

Example:
- Input: word1 = "horse", word2 = "ros"
- Output: 3
- Explanation: horse -> rorse -> rose -> ros

Requirements:
1. Use 2D DP with O(m*n) time complexity
2. Handle edge cases: empty strings, single char, identical strings
3. Optimize space if possible (O(min(m,n)) space)
4. Write comprehensive tests

Write to 'edit_distance.py' with a Solution class containing minDistance method.
"""
    }
]


@gcri_scope.observe('dp_test', priority=0, default=True, chain_with='no_reasoning')
def dp_test(config):
    """Configure for DP tasks with external memory."""
    config.protocols.max_iterations = 3
    config.branches_generator_type = 'adaptive'
    config.num_branches = 2
    config.task_domain = 'dp_algorithms'  # Domain hint for external memory
    return config


@gcri_scope
def main(config):
    print('=' * 70)
    print('External Memory Cross-Task Learning Test')
    print('Domain: Dynamic Programming Algorithms')
    print('=' * 70)

    # Clear previous external memory for clean test
    ext_memory_path = os.path.join(config.run_dir, 'external_memory.json')
    if os.path.exists(ext_memory_path):
        os.remove(ext_memory_path)
        print(f'🗑️ Cleared previous external memory')

    results = []
    
    for i, task in enumerate(DP_TASKS):
        print(f'\n{"="*70}')
        print(f'📝 Task {i+1}/3: {task["name"]}')
        print('=' * 70)
        
        # Show external memory state before task
        if os.path.exists(ext_memory_path):
            ext_mem = ExternalMemory(ext_memory_path)
            stats = ext_mem.stats
            print(f'🧠 External Memory Before: {stats["global_count"]} global, {stats["domain_counts"]}')
        else:
            print('🧠 External Memory Before: Empty')
        
        # Run GCRI
        gcri = GCRI(config)
        result = gcri(task['description'])
        
        decision = result.get('decision', False)
        results.append({
            'task': task['name'],
            'decision': decision
        })
        
        print(f'\n📊 Result: {"✅ SUCCESS" if decision else "❌ FAILED"}')
        
        # Show external memory state after task
        if os.path.exists(ext_memory_path):
            ext_mem = ExternalMemory(ext_memory_path)
            stats = ext_mem.stats
            print(f'🧠 External Memory After: {stats["global_count"]} global, {stats["domain_counts"]}')

    # Final summary
    print('\n' + '=' * 70)
    print('📊 FINAL SUMMARY')
    print('=' * 70)
    
    for r in results:
        status = '✅' if r['decision'] else '❌'
        print(f"  {status} {r['task']}")
    
    success_count = sum(1 for r in results if r['decision'])
    print(f'\n🎯 Total: {success_count}/3 tasks succeeded')
    
    # Show final external memory contents
    if os.path.exists(ext_memory_path):
        ext_mem = ExternalMemory(ext_memory_path)
        print(f'\n🧠 Final External Memory:')
        print(f'   Global rules: {len(ext_mem._data.get("global_rules", []))}')
        for domain, rules in ext_mem._data.get('domain_rules', {}).items():
            print(f'   {domain}: {len(rules)} rules')


if __name__ == '__main__':
    main()
