"""
Additional edge case tests for Dynamic Task Type Support

This test verifies edge cases and advanced scenarios:
1. Adding multiple custom task types at once
2. Removing custom task types and verifying they disappear from aggregated queries
3. Testing with projects that have no custom task types
4. Testing with mixed standard and custom task types
5. Verifying task type ordering in responses

Requirements: 3.4
"""

import requests
import json
import time
from typing import Dict, List, Any

BASE_URL = "http://localhost:8000"


def login() -> str:
    """Login and return access token"""
    response = requests.post(f"{BASE_URL}/auth/login", json={
        "email": "admin@vfx.com",
        "password": "admin123"
    })
    if response.status_code == 200:
        return response.json()["access_token"]
    else:
        raise Exception(f"Login failed: {response.status_code} - {response.text}")


def create_test_project(token: str, suffix: str = "") -> int:
    """Create a test project and return its ID"""
    headers = {"Authorization": f"Bearer {token}"}
    
    project_data = {
        "name": f"Edge Case Test Project {suffix} {int(time.time())}",
        "code_name": f"edge_test_{suffix}_{int(time.time())}",
        "client_name": "Test Client",
        "project_type": "tv",
        "description": f"Edge case test project {suffix}"
    }
    
    response = requests.post(f"{BASE_URL}/projects/", headers=headers, json=project_data)
    if response.status_code == 201:
        return response.json()["id"]
    else:
        raise Exception(f"Failed to create project: {response.status_code} - {response.text}")


def create_test_episode(token: str, project_id: int) -> int:
    """Create a test episode and return its ID"""
    headers = {"Authorization": f"Bearer {token}"}
    
    episode_data = {
        "name": "Test Episode",
        "episode_number": 1,
        "description": "Test episode for edge case testing"
    }
    
    response = requests.post(f"{BASE_URL}/episodes/projects/{project_id}/episodes", headers=headers, json=episode_data)
    if response.status_code == 201:
        return response.json()["id"]
    else:
        raise Exception(f"Failed to create episode: {response.status_code} - {response.text}")


def create_test_shot(token: str, episode_id: int, shot_name: str) -> int:
    """Create a test shot and return its ID"""
    headers = {"Authorization": f"Bearer {token}"}
    
    shot_data = {
        "name": shot_name,
        "description": f"Test shot {shot_name}",
        "frame_start": 1001,
        "frame_end": 1100
    }
    
    response = requests.post(f"{BASE_URL}/shots/?episode_id={episode_id}", headers=headers, json=shot_data)
    if response.status_code == 201:
        return response.json()["id"]
    else:
        raise Exception(f"Failed to create shot: {response.status_code} - {response.text}")


def create_test_asset(token: str, project_id: int, asset_name: str) -> int:
    """Create a test asset and return its ID"""
    headers = {"Authorization": f"Bearer {token}"}
    
    asset_data = {
        "name": asset_name,
        "category": "characters",
        "description": f"Test asset {asset_name}"
    }
    
    response = requests.post(f"{BASE_URL}/assets/?project_id={project_id}", headers=headers, json=asset_data)
    if response.status_code == 201:
        return response.json()["id"]
    else:
        raise Exception(f"Failed to create asset: {response.status_code} - {response.text}")


def add_custom_task_type(token: str, project_id: int, task_type: str, category: str) -> bool:
    """Add a custom task type to the project"""
    headers = {"Authorization": f"Bearer {token}"}
    
    data = {
        "task_type": task_type,
        "category": category
    }
    
    response = requests.post(f"{BASE_URL}/projects/{project_id}/custom-task-types", headers=headers, json=data)
    return response.status_code in [201, 409]  # 201 = created, 409 = already exists


def delete_custom_task_type(token: str, project_id: int, task_type: str, category: str) -> bool:
    """Delete a custom task type from the project"""
    headers = {"Authorization": f"Bearer {token}"}
    
    response = requests.delete(f"{BASE_URL}/projects/{project_id}/custom-task-types/{task_type}?category={category}", headers=headers)
    return response.status_code == 200


def get_shots_with_task_status(token: str, project_id: int) -> List[Dict[str, Any]]:
    """Get shots with their task status information"""
    headers = {"Authorization": f"Bearer {token}"}
    
    response = requests.get(f"{BASE_URL}/shots/?project_id={project_id}", headers=headers)
    if response.status_code == 200:
        return response.json()
    else:
        raise Exception(f"Failed to get shots: {response.status_code} - {response.text}")


def get_assets_with_task_status(token: str, project_id: int) -> List[Dict[str, Any]]:
    """Get assets with their task status information"""
    headers = {"Authorization": f"Bearer {token}"}
    
    response = requests.get(f"{BASE_URL}/assets/?project_id={project_id}", headers=headers)
    if response.status_code == 200:
        return response.json()
    else:
        raise Exception(f"Failed to get assets: {response.status_code} - {response.text}")


def test_multiple_custom_task_types():
    """Test adding multiple custom task types and verifying they all appear"""
    print("\n" + "="*60)
    print("TEST 1: Multiple Custom Task Types")
    print("="*60)
    
    token = login()
    project_id = create_test_project(token, "multi")
    episode_id = create_test_episode(token, project_id)
    shot_id = create_test_shot(token, episode_id, "MULTI_001")
    asset_id = create_test_asset(token, project_id, "MultiCharacter")
    
    # Add multiple custom task types
    custom_shot_types = ["fx_simulation", "matte_painting", "crowd_simulation"]
    custom_asset_types = ["grooming", "cloth_simulation", "facial_rigging"]
    
    print(f"Adding {len(custom_shot_types)} custom shot task types...")
    for task_type in custom_shot_types:
        success = add_custom_task_type(token, project_id, task_type, "shot")
        print(f"  {task_type}: {'✓' if success else '✗'}")
    
    print(f"Adding {len(custom_asset_types)} custom asset task types...")
    for task_type in custom_asset_types:
        success = add_custom_task_type(token, project_id, task_type, "asset")
        print(f"  {task_type}: {'✓' if success else '✗'}")
    
    # Verify all custom types appear in aggregated responses
    shots = get_shots_with_task_status(token, project_id)
    assets = get_assets_with_task_status(token, project_id)
    
    shot_task_types = list(shots[0].get('task_status', {}).keys()) if shots else []
    asset_task_types = list(assets[0].get('task_status', {}).keys()) if assets else []
    
    shot_success = all(task_type in shot_task_types for task_type in custom_shot_types)
    asset_success = all(task_type in asset_task_types for task_type in custom_asset_types)
    
    print(f"All custom shot types in response: {'✓ PASS' if shot_success else '✗ FAIL'}")
    print(f"All custom asset types in response: {'✓ PASS' if asset_success else '✗ FAIL'}")
    
    return shot_success and asset_success


def test_task_type_removal():
    """Test removing custom task types and verifying they disappear from responses"""
    print("\n" + "="*60)
    print("TEST 2: Task Type Removal")
    print("="*60)
    
    token = login()
    project_id = create_test_project(token, "removal")
    episode_id = create_test_episode(token, project_id)
    shot_id = create_test_shot(token, episode_id, "REMOVE_001")
    asset_id = create_test_asset(token, project_id, "RemoveCharacter")
    
    # Add custom task types
    custom_shot_type = "temp_fx"
    custom_asset_type = "temp_grooming"
    
    add_custom_task_type(token, project_id, custom_shot_type, "shot")
    add_custom_task_type(token, project_id, custom_asset_type, "asset")
    
    # Verify they appear
    shots_before = get_shots_with_task_status(token, project_id)
    assets_before = get_assets_with_task_status(token, project_id)
    
    shot_types_before = list(shots_before[0].get('task_status', {}).keys()) if shots_before else []
    asset_types_before = list(assets_before[0].get('task_status', {}).keys()) if assets_before else []
    
    print(f"Custom types present before removal:")
    print(f"  Shot '{custom_shot_type}': {custom_shot_type in shot_types_before}")
    print(f"  Asset '{custom_asset_type}': {custom_asset_type in asset_types_before}")
    
    # Remove custom task types
    shot_removed = delete_custom_task_type(token, project_id, custom_shot_type, "shot")
    asset_removed = delete_custom_task_type(token, project_id, custom_asset_type, "asset")
    
    print(f"Removal success:")
    print(f"  Shot type removed: {'✓' if shot_removed else '✗'}")
    print(f"  Asset type removed: {'✓' if asset_removed else '✗'}")
    
    # Verify they disappear from responses
    shots_after = get_shots_with_task_status(token, project_id)
    assets_after = get_assets_with_task_status(token, project_id)
    
    shot_types_after = list(shots_after[0].get('task_status', {}).keys()) if shots_after else []
    asset_types_after = list(assets_after[0].get('task_status', {}).keys()) if assets_after else []
    
    shot_disappeared = custom_shot_type not in shot_types_after
    asset_disappeared = custom_asset_type not in asset_types_after
    
    print(f"Custom types absent after removal:")
    print(f"  Shot '{custom_shot_type}': {'✓ PASS' if shot_disappeared else '✗ FAIL'}")
    print(f"  Asset '{custom_asset_type}': {'✓ PASS' if asset_disappeared else '✗ FAIL'}")
    
    return shot_disappeared and asset_disappeared


def test_standard_types_consistency():
    """Test that standard task types are always present regardless of custom types"""
    print("\n" + "="*60)
    print("TEST 3: Standard Types Consistency")
    print("="*60)
    
    token = login()
    
    # Test with project that has no custom types
    project_id_1 = create_test_project(token, "standard1")
    episode_id_1 = create_test_episode(token, project_id_1)
    shot_id_1 = create_test_shot(token, episode_id_1, "STD_001")
    asset_id_1 = create_test_asset(token, project_id_1, "StandardCharacter1")
    
    # Test with project that has custom types
    project_id_2 = create_test_project(token, "standard2")
    episode_id_2 = create_test_episode(token, project_id_2)
    shot_id_2 = create_test_shot(token, episode_id_2, "STD_002")
    asset_id_2 = create_test_asset(token, project_id_2, "StandardCharacter2")
    
    add_custom_task_type(token, project_id_2, "custom_fx", "shot")
    add_custom_task_type(token, project_id_2, "custom_grooming", "asset")
    
    # Expected standard types
    expected_shot_types = ["layout", "animation", "simulation", "lighting", "compositing"]
    expected_asset_types = ["modeling", "surfacing", "rigging"]
    
    # Check both projects
    shots_1 = get_shots_with_task_status(token, project_id_1)
    assets_1 = get_assets_with_task_status(token, project_id_1)
    shots_2 = get_shots_with_task_status(token, project_id_2)
    assets_2 = get_assets_with_task_status(token, project_id_2)
    
    shot_types_1 = list(shots_1[0].get('task_status', {}).keys()) if shots_1 else []
    asset_types_1 = list(assets_1[0].get('task_status', {}).keys()) if assets_1 else []
    shot_types_2 = list(shots_2[0].get('task_status', {}).keys()) if shots_2 else []
    asset_types_2 = list(assets_2[0].get('task_status', {}).keys()) if assets_2 else []
    
    # Verify standard types are present in both projects
    shot_std_1 = all(t in shot_types_1 for t in expected_shot_types)
    asset_std_1 = all(t in asset_types_1 for t in expected_asset_types)
    shot_std_2 = all(t in shot_types_2 for t in expected_shot_types)
    asset_std_2 = all(t in asset_types_2 for t in expected_asset_types)
    
    print(f"Project 1 (no custom types):")
    print(f"  Standard shot types present: {'✓ PASS' if shot_std_1 else '✗ FAIL'}")
    print(f"  Standard asset types present: {'✓ PASS' if asset_std_1 else '✗ FAIL'}")
    
    print(f"Project 2 (with custom types):")
    print(f"  Standard shot types present: {'✓ PASS' if shot_std_2 else '✗ FAIL'}")
    print(f"  Standard asset types present: {'✓ PASS' if asset_std_2 else '✗ FAIL'}")
    
    # Verify custom types are only in project 2
    custom_in_1 = "custom_fx" in shot_types_1 or "custom_grooming" in asset_types_1
    custom_in_2 = "custom_fx" in shot_types_2 and "custom_grooming" in asset_types_2
    
    print(f"Custom types correctly isolated:")
    print(f"  No custom types in project 1: {'✓ PASS' if not custom_in_1 else '✗ FAIL'}")
    print(f"  Custom types present in project 2: {'✓ PASS' if custom_in_2 else '✗ FAIL'}")
    
    return shot_std_1 and asset_std_1 and shot_std_2 and asset_std_2 and not custom_in_1 and custom_in_2


def test_dynamic_task_type_edge_cases():
    """Main test function for edge cases"""
    print("=" * 80)
    print("TESTING DYNAMIC TASK TYPE EDGE CASES")
    print("=" * 80)
    
    try:
        results = []
        
        # Run all edge case tests
        results.append(test_multiple_custom_task_types())
        results.append(test_task_type_removal())
        results.append(test_standard_types_consistency())
        
        # Summary
        print("\n" + "=" * 80)
        print("EDGE CASE TEST RESULTS SUMMARY")
        print("=" * 80)
        
        test_names = [
            "Multiple Custom Task Types",
            "Task Type Removal",
            "Standard Types Consistency"
        ]
        
        passed = sum(results)
        total = len(results)
        
        for i, (name, result) in enumerate(zip(test_names, results)):
            print(f"{i+1}. {name}: {'✓ PASS' if result else '✗ FAIL'}")
        
        print(f"\nOverall Result: {passed}/{total} edge case tests passed")
        
        if passed == total:
            print("🎉 ALL EDGE CASE TESTS PASSED - Dynamic task type support is robust!")
            return True
        else:
            print("❌ SOME EDGE CASE TESTS FAILED - Dynamic task type support needs attention")
            return False
        
    except Exception as e:
        print(f"\n❌ EDGE CASE TEST ERROR: {str(e)}")
        return False


if __name__ == "__main__":
    success = test_dynamic_task_type_edge_cases()
    exit(0 if success else 1)