#!/usr/bin/env python3
"""
Endpoint tests that mimic web browser requests.
Tests run against the device at 192.168.4.1
"""

import os
import pytest

if os.environ.get("LED_CONTROLLER_RUN_DEVICE_ENDPOINT_TESTS") != "1":
    pytest.skip(
        "Legacy device integration endpoint tests; enable explicitly to run.",
        allow_module_level=True,
    )

import requests
import json
import sys
from typing import Dict, Optional

# Base URL for the device
BASE_URL = "http://192.168.4.1"

class TestClient:
    """HTTP client that mimics a web browser with cookie support."""
    
    def __init__(self, base_url: str = BASE_URL):
        self.base_url = base_url
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
            'Accept': 'application/json',
            'Accept-Language': 'en-US,en;q=0.9',
        })
    
    def get(self, path: str, **kwargs) -> requests.Response:
        """GET request."""
        url = f"{self.base_url}{path}"
        return self.session.get(url, **kwargs)
    
    def post(self, path: str, data: Optional[Dict] = None, json_data: Optional[Dict] = None, **kwargs) -> requests.Response:
        """POST request."""
        url = f"{self.base_url}{path}"
        if json_data:
            return self.session.post(url, json=json_data, **kwargs)
        return self.session.post(url, data=data, **kwargs)
    
    def put(self, path: str, json_data: Optional[Dict] = None, **kwargs) -> requests.Response:
        """PUT request."""
        url = f"{self.base_url}{path}"
        return self.session.put(url, json=json_data, **kwargs)
    
    def delete(self, path: str, **kwargs) -> requests.Response:
        """DELETE request."""
        url = f"{self.base_url}{path}"
        return self.session.delete(url, **kwargs)
    
    def set_cookie(self, name: str, value: str):
        """Set a cookie manually."""
        self.session.cookies.set(name, value, domain='192.168.4.1', path='/')
    
    def get_cookie(self, name: str) -> Optional[str]:
        """Get a cookie value."""
        return self.session.cookies.get(name)

def test_connection(client: TestClient) -> bool:
    """Test basic connection to the server."""
    print("Testing connection...")
    try:
        response = client.get('/')
        if response.status_code == 200:
            print("✓ Connection successful")
            return True
        else:
            print(f"✗ Connection failed: {response.status_code}")
            return False
    except requests.exceptions.ConnectionError:
        print(f"✗ Cannot connect to {BASE_URL}")
        print("  Make sure the device is running and accessible at 192.168.4.1")
        return False
    except Exception as e:
        print(f"✗ Connection error: {e}")
        return False

def test_zones(client: TestClient) -> bool:
    """Test zones endpoints."""
    print("\n=== Testing Zones Endpoints ===")
    passed = 0
    total = 0
    
    # Test 1: List zones
    total += 1
    try:
        response = client.get('/zones')
        if response.status_code == 200:
            data = response.json()
            print(f"✓ GET /zones - Found {len(data.get('zones', {}))} zones")
            passed += 1
        else:
            print(f"✗ GET /zones - Status: {response.status_code}")
    except Exception as e:
        print(f"✗ GET /zones - Error: {e}")
    
    # Test 2: Create zone
    total += 1
    try:
        zone_data = {
            "name": "Test Zone",
            "names": ["1", "2"]
        }
        response = client.post('/zones', json_data=zone_data)
        if response.status_code == 201:
            created_zone = response.json()
            # Response format: {zone_id: {zone object}}
            if isinstance(created_zone, dict):
                # Get the first key which should be the zone ID
                zone_id = next(iter(created_zone.keys())) if created_zone else None
            else:
                zone_id = None
            print(f"✓ POST /zones - Created zone: {zone_id}")
            passed += 1
            
            # Test 3: Get specific zone
            if zone_id:
                total += 1
                response = client.get(f'/zones/{zone_id}')
                if response.status_code == 200:
                    print(f"✓ GET /zones/{zone_id} - Retrieved zone")
                    passed += 1
                else:
                    print(f"✗ GET /zones/{zone_id} - Status: {response.status_code}")
                
                # Test 4: Set current zone
                total += 1
                response = client.post(f'/zones/{zone_id}/set-current')
                if response.status_code == 200:
                    print(f"✓ POST /zones/{zone_id}/set-current - Set current zone")
                    # Check cookie was set
                    cookie = client.get_cookie('current_zone')
                    if cookie == zone_id:
                        print(f"  ✓ Cookie 'current_zone' set to {zone_id}")
                    passed += 1
                else:
                    print(f"✗ POST /zones/{zone_id}/set-current - Status: {response.status_code}")
                
                # Test 5: Get current zone
                total += 1
                response = client.get('/zones/current')
                if response.status_code == 200:
                    data = response.json()
                    if data.get('zone_id') == zone_id:
                        print(f"✓ GET /zones/current - Current zone is {zone_id}")
                        passed += 1
                    else:
                        print(f"✗ GET /zones/current - Wrong zone ID")
                else:
                    print(f"✗ GET /zones/current - Status: {response.status_code}")
                
                # Test 6: Update zone (edit functionality)
                total += 1
                update_data = {
                    "name": "Updated Test Zone",
                    "names": ["1", "2", "3"]  # Update device IDs too
                }
                response = client.put(f'/zones/{zone_id}', json_data=update_data)
                if response.status_code == 200:
                    updated = response.json()
                    if updated.get('name') == "Updated Test Zone" and updated.get('names') == ["1", "2", "3"]:
                        print(f"✓ PUT /zones/{zone_id} - Updated zone (name and device IDs)")
                        passed += 1
                    else:
                        print(f"✗ PUT /zones/{zone_id} - Update didn't work correctly")
                        print(f"  Expected name='Updated Test Zone', got '{updated.get('name')}'")
                        print(f"  Expected names=['1','2','3'], got {updated.get('names')}")
                else:
                    print(f"✗ PUT /zones/{zone_id} - Status: {response.status_code}, Response: {response.text}")
                
                # Test 6b: Verify update persisted
                total += 1
                response = client.get(f'/zones/{zone_id}')
                if response.status_code == 200:
                    verified = response.json()
                    if verified.get('name') == "Updated Test Zone":
                        print(f"✓ GET /zones/{zone_id} - Verified update persisted")
                        passed += 1
                    else:
                        print(f"✗ GET /zones/{zone_id} - Update didn't persist")
                else:
                    print(f"✗ GET /zones/{zone_id} - Status: {response.status_code}")
                
                # Test 7: Delete zone
                total += 1
                response = client.delete(f'/zones/{zone_id}')
                if response.status_code == 200:
                    print(f"✓ DELETE /zones/{zone_id} - Deleted zone")
                    passed += 1
                else:
                    print(f"✗ DELETE /zones/{zone_id} - Status: {response.status_code}")
        else:
            print(f"✗ POST /zones - Status: {response.status_code}, Response: {response.text}")
    except Exception as e:
        print(f"✗ POST /zones - Error: {e}")
        import traceback
        traceback.print_exc()
    
    print(f"\nZones tests: {passed}/{total} passed")
    return passed == total

def test_profiles(client: TestClient) -> bool:
    """Test profiles endpoints."""
    print("\n=== Testing Profiles Endpoints ===")
    passed = 0
    total = 0
    
    # Test 1: List profiles
    total += 1
    try:
        response = client.get('/profiles')
        if response.status_code == 200:
            data = response.json()
            profiles = data.get('profiles', {})
            current_id = data.get('current_profile_id')
            print(f"✓ GET /profiles - Found {len(profiles)} profiles, current: {current_id}")
            passed += 1
        else:
            print(f"✗ GET /profiles - Status: {response.status_code}")
    except Exception as e:
        print(f"✗ GET /profiles - Error: {e}")
    
    # Test 2: Get current profile
    total += 1
    try:
        response = client.get('/profiles/current')
        if response.status_code == 200:
            data = response.json()
            print(f"✓ GET /profiles/current - Current profile: {data.get('id')}")
            passed += 1
        else:
            print(f"✗ GET /profiles/current - Status: {response.status_code}")
    except Exception as e:
        print(f"✗ GET /profiles/current - Error: {e}")
    
    # Test 3: Create profile
    total += 1
    try:
        profile_data = {"name": "Test Profile"}
        response = client.post('/profiles', json_data=profile_data)
        if response.status_code == 201:
            created = response.json()
            # Response format: {profile_id: {profile_data}}
            if isinstance(created, dict):
                profile_id = next(iter(created.keys())) if created else None
            else:
                profile_id = None
            print(f"✓ POST /profiles - Created profile: {profile_id}")
            passed += 1
            
            # Test 4: Apply profile
            if profile_id:
                total += 1
                response = client.post(f'/profiles/{profile_id}/apply')
                if response.status_code == 200:
                    print(f"✓ POST /profiles/{profile_id}/apply - Applied profile")
                    passed += 1
                else:
                    print(f"✗ POST /profiles/{profile_id}/apply - Status: {response.status_code}")
                
                # Test 5: Delete profile
                total += 1
                response = client.delete(f'/profiles/{profile_id}')
                if response.status_code == 200:
                    print(f"✓ DELETE /profiles/{profile_id} - Deleted profile")
                    passed += 1
                else:
                    print(f"✗ DELETE /profiles/{profile_id} - Status: {response.status_code}")
        else:
            print(f"✗ POST /profiles - Status: {response.status_code}")
    except Exception as e:
        print(f"✗ POST /profiles - Error: {e}")
    
    print(f"\nProfiles tests: {passed}/{total} passed")
    return passed == total

def test_presets(client: TestClient) -> bool:
    """Test presets endpoints."""
    print("\n=== Testing Presets Endpoints ===")
    passed = 0
    total = 0
    
    # Test 1: List presets
    total += 1
    try:
        response = client.get('/presets')
        if response.status_code == 200:
            data = response.json()
            preset_count = len(data) if isinstance(data, dict) else 0
            print(f"✓ GET /presets - Found {preset_count} presets")
            passed += 1
        else:
            print(f"✗ GET /presets - Status: {response.status_code}")
    except Exception as e:
        print(f"✗ GET /presets - Error: {e}")
    
    # Test 2: Create preset
    total += 1
    try:
        preset_data = {
            "name": "Test Preset",
            "pattern": "on",
            "colors": ["#ff0000"],
            "brightness": 200
        }
        response = client.post('/presets', json_data=preset_data)
        if response.status_code == 201:
            created = response.json()
            # Response format: {preset_id: {preset_data}}
            if isinstance(created, dict):
                preset_id = next(iter(created.keys())) if created else None
            else:
                preset_id = None
            print(f"✓ POST /presets - Created preset: {preset_id}")
            passed += 1
            
            # Test 3: Get specific preset
            if preset_id:
                total += 1
                response = client.get(f'/presets/{preset_id}')
                if response.status_code == 200:
                    print(f"✓ GET /presets/{preset_id} - Retrieved preset")
                    passed += 1
                else:
                    print(f"✗ GET /presets/{preset_id} - Status: {response.status_code}")
                
                # Test 4: Update preset
                total += 1
                update_data = {"brightness": 150}
                response = client.put(f'/presets/{preset_id}', json_data=update_data)
                if response.status_code == 200:
                    print(f"✓ PUT /presets/{preset_id} - Updated preset")
                    passed += 1
                else:
                    print(f"✗ PUT /presets/{preset_id} - Status: {response.status_code}")
                
                # Test 5: Send preset via /presets/send
                total += 1
                try:
                    send_body = {"preset_ids": [preset_id]}
                    response = client.post('/presets/send', json_data=send_body)
                    if response.status_code == 200:
                        data = response.json()
                        sent = data.get('presets_sent')
                        print(f"✓ POST /presets/send - Sent presets (presets_sent={sent})")
                        passed += 1
                    else:
                        print(f"✗ POST /presets/send - Status: {response.status_code}, Response: {response.text}")
                except Exception as e:
                    print(f"✗ POST /presets/send - Error: {e}")
                
                # Test 6: Delete preset
                total += 1
                response = client.delete(f'/presets/{preset_id}')
                if response.status_code == 200:
                    print(f"✓ DELETE /presets/{preset_id} - Deleted preset")
                    passed += 1
                else:
                    print(f"✗ DELETE /presets/{preset_id} - Status: {response.status_code}")
        else:
            print(f"✗ POST /presets - Status: {response.status_code}")
    except Exception as e:
        print(f"✗ POST /presets - Error: {e}")
    
    print(f"\nPresets tests: {passed}/{total} passed")
    return passed == total

def test_patterns(client: TestClient) -> bool:
    """Test patterns endpoints."""
    print("\n=== Testing Patterns Endpoints ===")
    passed = 0
    total = 0
    
    # Test 1: List patterns
    total += 1
    try:
        response = client.get('/patterns')
        if response.status_code == 200:
            data = response.json()
            pattern_count = len(data) if isinstance(data, dict) else 0
            print(f"✓ GET /patterns - Found {pattern_count} patterns")
            passed += 1
        else:
            print(f"✗ GET /patterns - Status: {response.status_code}")
    except Exception as e:
        print(f"✗ GET /patterns - Error: {e}")
    
    # Test 2: Get pattern definitions
    total += 1
    try:
        response = client.get('/patterns/definitions')
        if response.status_code == 200:
            data = response.json()
            print(f"✓ GET /patterns/definitions - Retrieved definitions")
            passed += 1
        else:
            print(f"✗ GET /patterns/definitions - Status: {response.status_code}")
    except Exception as e:
        print(f"✗ GET /patterns/definitions - Error: {e}")
    
    # Test 3: Firmware-only on/off — no OTA file (400 from API).
    total += 1
    try:
        r = client.get("/patterns/ota/file/on.py")
        if r.status_code == 400 and "error" in r.json():
            print("✓ GET /patterns/ota/file/on.py - Rejected as built-in")
            passed += 1
        else:
            print(f"✗ GET /patterns/ota/file/on.py - Expected 400, got {r.status_code}")
    except Exception as e:
        print(f"✗ GET /patterns/ota/file/on.py - Error: {e}")

    total += 1
    try:
        r = client.post("/patterns/off/send", json_data={})
        if r.status_code == 400 and "error" in r.json():
            print("✓ POST /patterns/off/send - Rejected as built-in")
            passed += 1
        else:
            print(f"✗ POST /patterns/off/send - Expected 400, got {r.status_code}")
    except Exception as e:
        print(f"✗ POST /patterns/off/send - Error: {e}")
    
    print(f"\nPatterns tests: {passed}/{total} passed")
    return passed == total

def test_zone_edit_workflow(client: TestClient) -> bool:
    """Test complete zone edit workflow like a browser would."""
    print("\n=== Testing Zone Edit Workflow ===")
    passed = 0
    total = 0
    
    # Step 1: Create a zone to edit
    total += 1
    try:
        zone_data = {
            "name": "Zone to Edit",
            "names": ["1"]
        }
        response = client.post('/zones', json_data=zone_data)
        if response.status_code == 201:
            created = response.json()
            if isinstance(created, dict):
                zone_id = next(iter(created.keys())) if created else None
            else:
                zone_id = None
            
            if zone_id:
                print(f"✓ Created zone {zone_id} for editing")
                passed += 1
                
                # Step 2: Get the zone to verify initial state
                total += 1
                response = client.get(f'/zones/{zone_id}')
                if response.status_code == 200:
                    original_zone = response.json()
                    print(f"✓ Retrieved zone - Name: '{original_zone.get('name')}', IDs: {original_zone.get('names')}")
                    passed += 1
                    
                    # Step 3: Edit the zone (simulate browser edit form submission)
                    total += 1
                    edit_data = {
                        "name": "Edited Zone Name",
                        "names": ["2", "3", "4"]
                    }
                    response = client.put(f'/zones/{zone_id}', json_data=edit_data)
                    if response.status_code == 200:
                        edited = response.json()
                        if edited.get('name') == "Edited Zone Name" and edited.get('names') == ["2", "3", "4"]:
                            print(f"✓ PUT /zones/{zone_id} - Successfully edited zone")
                            print(f"  New name: '{edited.get('name')}'")
                            print(f"  New device IDs: {edited.get('names')}")
                            passed += 1
                        else:
                            print(f"✗ PUT /zones/{zone_id} - Edit didn't work correctly")
                            print(f"  Got: {edited}")
                    else:
                        print(f"✗ PUT /zones/{zone_id} - Status: {response.status_code}, Response: {response.text}")
                    
                    # Step 4: Verify edit persisted by getting the zone again
                    total += 1
                    response = client.get(f'/zones/{zone_id}')
                    if response.status_code == 200:
                        verified = response.json()
                        if verified.get('name') == "Edited Zone Name" and verified.get('names') == ["2", "3", "4"]:
                            print(f"✓ GET /zones/{zone_id} - Verified edit persisted")
                            passed += 1
                        else:
                            print(f"✗ GET /zones/{zone_id} - Edit didn't persist")
                            print(f"  Expected name='Edited Zone Name', got '{verified.get('name')}'")
                            print(f"  Expected names=['2','3','4'], got {verified.get('names')}")
                    else:
                        print(f"✗ GET /zones/{zone_id} - Status: {response.status_code}")
                    
                    # Step 5: Clean up - delete the test zone
                    total += 1
                    response = client.delete(f'/zones/{zone_id}')
                    if response.status_code == 200:
                        print(f"✓ DELETE /zones/{zone_id} - Cleaned up test zone")
                        passed += 1
                    else:
                        print(f"✗ DELETE /zones/{zone_id} - Status: {response.status_code}")
            else:
                print(f"✗ Failed to extract zone ID from create response")
        else:
            print(f"✗ POST /zones - Status: {response.status_code}, Response: {response.text}")
    except Exception as e:
        print(f"✗ Zone edit workflow - Error: {e}")
        import traceback
        traceback.print_exc()
    
    print(f"\nZone edit workflow tests: {passed}/{total} passed")
    return passed == total

def test_static_files(client: TestClient) -> bool:
    """Test static file serving."""
    print("\n=== Testing Static Files ===")
    passed = 0
    total = 0
    
    static_files = [
        '/static/style.css',
        '/static/app.js',
        '/static/zones.js',
        '/static/presets.js',
        '/static/profiles.js',
        '/static/devices.js',
    ]
    
    for file_path in static_files:
        total += 1
        try:
            response = client.get(file_path)
            if response.status_code == 200:
                print(f"✓ GET {file_path} - Retrieved")
                passed += 1
            else:
                print(f"✗ GET {file_path} - Status: {response.status_code}")
        except Exception as e:
            print(f"✗ GET {file_path} - Error: {e}")
    
    print(f"\nStatic files tests: {passed}/{total} passed")
    return passed == total

def main():
    """Run all endpoint tests."""
    print("=" * 60)
    print("LED Controller Endpoint Tests")
    print(f"Testing against: {BASE_URL}")
    print("=" * 60)
    
    client = TestClient()
    
    # Test connection first
    if not test_connection(client):
        print("\n✗ Cannot connect to device. Exiting.")
        sys.exit(1)
    
    results = []
    
    # Run all tests
    results.append(("Zones", test_zones(client)))
    results.append(("Zone Edit Workflow", test_zone_edit_workflow(client)))
    results.append(("Profiles", test_profiles(client)))
    results.append(("Presets", test_presets(client)))
    results.append(("Patterns", test_patterns(client)))
    results.append(("Static Files", test_static_files(client)))
    
    # Summary
    print("\n" + "=" * 60)
    print("Test Summary")
    print("=" * 60)
    all_passed = True
    for name, passed in results:
        status = "✓ PASS" if passed else "✗ FAIL"
        print(f"{status} - {name}")
        if not passed:
            all_passed = False
    
    print("=" * 60)
    if all_passed:
        print("✓ All tests passed!")
        sys.exit(0)
    else:
        print("✗ Some tests failed")
        sys.exit(1)

if __name__ == "__main__":
    main()
