#!/usr/bin/env python3
"""
🔒 KAPSAMLI GÜVENLİK VE STRES TESTİ
===================================
1. Tüm endpoint'leri test et
2. Memory leak kontrolü
3. Connection leak kontrolü
4. Rate limiting testi
5. Error handling testi
6. Circuit breaker testi
7. SQL injection testi
8. XSS/Header injection testi
"""

import asyncio
import aiohttp
import time
import json
import random
import sys
from dataclasses import dataclass
from typing import List, Dict, Any
from collections import defaultdict

API_URL = "http://botapi.componentmarket.org"
# API_URL = "http://localhost:4000"

@dataclass
class TestResult:
    test_name: str
    passed: bool
    message: str
    duration_ms: float = 0
    details: Dict[str, Any] = None

class ComprehensiveTester:
    def __init__(self, base_url: str):
        self.base_url = base_url
        self.results: List[TestResult] = []
        self.admin_token = "1"
        
    async def run_all_tests(self):
        print("="*70)
        print("🔒 KAPSAMLI GÜVENLİK VE STRES TESTİ")
        print("="*70)
        print(f"🌐 Hedef: {self.base_url}\n")
        
        # 1. Endpoint Discovery & Basic Tests
        await self.test_all_endpoints()
        
        # 2. Security Tests
        await self.test_sql_injection()
        await self.test_xss_injection()
        await self.test_header_injection()
        
        # 3. Rate Limiting Tests
        await self.test_rate_limiting()
        
        # 4. Error Handling Tests
        await self.test_error_handling()
        
        # 5. Memory/Connection Leak Test
        await self.test_connection_leaks()
        
        # 6. Circuit Breaker Test
        await self.test_circuit_breaker()
        
        # 7. Concurrent Stress Test
        await self.test_concurrent_stress()
        
        # Print Summary
        self.print_summary()
    
    async def make_request(self, method: str, endpoint: str, data: dict = None, 
                          headers: dict = None, timeout: int = 30) -> tuple:
        """Make HTTP request and return (status, response, duration)"""
        url = f"{self.base_url}{endpoint}"
        start = time.time()
        
        default_headers = {"Content-Type": "application/json"}
        if headers:
            default_headers.update(headers)
        
        try:
            async with aiohttp.ClientSession() as session:
                if method == "GET":
                    async with session.get(url, headers=default_headers, 
                                          timeout=aiohttp.ClientTimeout(total=timeout)) as resp:
                        body = await resp.text()
                        duration = (time.time() - start) * 1000
                        return resp.status, body, duration
                else:
                    async with session.post(url, json=data, headers=default_headers,
                                           timeout=aiohttp.ClientTimeout(total=timeout)) as resp:
                        body = await resp.text()
                        duration = (time.time() - start) * 1000
                        return resp.status, body, duration
        except asyncio.TimeoutError:
            return 0, "Timeout", (time.time() - start) * 1000
        except Exception as e:
            return 0, str(e), (time.time() - start) * 1000
    
    async def test_all_endpoints(self):
        """Test all API endpoints"""
        print("\n" + "="*60)
        print("📋 1. TÜM ENDPOİNT TESTİ")
        print("="*60)
        
        endpoints = [
            ("GET", "/api/health", None),
            ("GET", "/api/metrics", None),
            ("GET", "/api/store/bots", None),
            ("GET", "/api/store/bot/sahibinden", None),
            ("GET", "/api/updates/manifest/launcher", None),
            ("GET", "/api/updates/check-delta/launcher?currentVersion=1.0.0", None),
            ("POST", "/api/auth/login", {"username": "test", "password": "test"}),
            ("POST", "/api/user/heartbeat", {"userId": 1, "machineId": "test", "currentPage": "test"}),
            ("GET", "/api/admin/dashboard", {"Authorization": f"Bearer {self.admin_token}"}),
            ("GET", "/api/admin/users", {"Authorization": f"Bearer {self.admin_token}"}),
            ("GET", "/api/admin/bot/sahibinden/scripts", {"Authorization": f"Bearer {self.admin_token}"}),
        ]
        
        passed = 0
        failed = 0
        
        for method, endpoint, extra in endpoints:
            headers = extra if isinstance(extra, dict) and "Authorization" in extra else None
            data = extra if isinstance(extra, dict) and "Authorization" not in extra else None
            
            status, body, duration = await self.make_request(method, endpoint, data, headers)
            
            success = status in [200, 201, 400, 401, 404]  # Expected responses
            icon = "✅" if success else "❌"
            print(f"   {icon} {method} {endpoint[:50]:50} → {status} ({duration:.0f}ms)")
            
            if success:
                passed += 1
            else:
                failed += 1
        
        self.results.append(TestResult(
            "Endpoint Tests", 
            failed == 0, 
            f"{passed}/{passed+failed} endpoints working",
            details={"passed": passed, "failed": failed}
        ))
    
    async def test_sql_injection(self):
        """Test SQL injection vulnerabilities"""
        print("\n" + "="*60)
        print("🔒 2. SQL INJECTION TESTİ")
        print("="*60)
        
        payloads = [
            "'; DROP TABLE users; --",
            "1 OR 1=1",
            "1' OR '1'='1",
            "admin'--",
            "1; SELECT * FROM users",
            "UNION SELECT password FROM users"
        ]
        
        vulnerable = False
        
        for payload in payloads:
            # Test in login
            status, body, _ = await self.make_request("POST", "/api/auth/login", 
                                                       {"username": payload, "password": payload})
            
            if "error" in body.lower() and ("sql" in body.lower() or "syntax" in body.lower()):
                print(f"   ⚠️ Potential SQL leak with: {payload[:30]}...")
                vulnerable = True
            else:
                print(f"   ✅ Safe from: {payload[:30]}...")
        
        self.results.append(TestResult(
            "SQL Injection", 
            not vulnerable, 
            "Protected from SQL injection" if not vulnerable else "Potential vulnerability"
        ))
    
    async def test_xss_injection(self):
        """Test XSS vulnerabilities"""
        print("\n" + "="*60)
        print("🔒 3. XSS TESTİ")
        print("="*60)
        
        payloads = [
            "<script>alert('xss')</script>",
            "javascript:alert('xss')",
            "<img src=x onerror=alert('xss')>",
            "{{constructor.constructor('alert(1)')()}}"
        ]
        
        vulnerable = False
        
        for payload in payloads:
            status, body, _ = await self.make_request("POST", "/api/auth/login",
                                                       {"username": payload, "password": "test"})
            
            # Check if payload is reflected without sanitization
            if payload in body and "error" not in body.lower():
                print(f"   ⚠️ XSS reflected: {payload[:30]}...")
                vulnerable = True
            else:
                print(f"   ✅ Safe from: {payload[:30]}...")
        
        self.results.append(TestResult(
            "XSS Injection", 
            not vulnerable, 
            "Protected from XSS" if not vulnerable else "Potential XSS vulnerability"
        ))
    
    async def test_header_injection(self):
        """Test HTTP header injection"""
        print("\n" + "="*60)
        print("🔒 4. HEADER INJECTİON TESTİ")
        print("="*60)
        
        # Test for HTTP response splitting
        malicious_headers = {
            "X-Test": "value\r\nSet-Cookie: hacked=true",
            "User-Agent": "test\r\nX-Injected: hacked"
        }
        
        vulnerable = False
        
        for header, value in malicious_headers.items():
            try:
                status, body, _ = await self.make_request("GET", "/api/health", headers={header: value})
                print(f"   ✅ Safe from {header} injection")
            except Exception as e:
                if "newline" in str(e).lower() or "invalid" in str(e).lower():
                    print(f"   ✅ Properly rejected {header} injection")
                else:
                    print(f"   ⚠️ Unexpected: {e}")
        
        self.results.append(TestResult(
            "Header Injection", 
            not vulnerable, 
            "Protected from header injection"
        ))
    
    async def test_rate_limiting(self):
        """Test rate limiting functionality"""
        print("\n" + "="*60)
        print("🚦 5. RATE LİMİTİNG TESTİ")
        print("="*60)
        
        # Make many rapid requests
        connector = aiohttp.TCPConnector(limit=100)
        async with aiohttp.ClientSession(connector=connector) as session:
            tasks = []
            for _ in range(100):
                tasks.append(session.get(f"{self.base_url}/api/health", 
                                        timeout=aiohttp.ClientTimeout(total=10)))
            
            start = time.time()
            responses = await asyncio.gather(*tasks, return_exceptions=True)
            duration = time.time() - start
            
            status_counts = defaultdict(int)
            for resp in responses:
                if isinstance(resp, Exception):
                    status_counts["error"] += 1
                else:
                    status_counts[resp.status] += 1
                    await resp.text()  # Consume body
        
        rate_limited = status_counts.get(429, 0)
        success = status_counts.get(200, 0)
        
        print(f"   📊 100 istek in {duration:.2f}s")
        print(f"   ✅ Başarılı: {success}")
        print(f"   🚫 Rate Limited (429): {rate_limited}")
        print(f"   ❌ Hata: {status_counts.get('error', 0)}")
        
        # Rate limiting should kick in
        rate_limiting_works = rate_limited > 0 or success == 100
        
        self.results.append(TestResult(
            "Rate Limiting", 
            rate_limiting_works, 
            f"Rate limiting active: {rate_limited} blocked" if rate_limited > 0 else "All requests passed (good capacity)"
        ))
    
    async def test_error_handling(self):
        """Test error handling"""
        print("\n" + "="*60)
        print("⚠️ 6. HATA YAKALAMA TESTİ")
        print("="*60)
        
        test_cases = [
            ("GET", "/api/nonexistent", 404),
            ("POST", "/api/auth/login", 400),  # Missing data
            ("GET", "/api/admin/dashboard", 401),  # No auth
            ("POST", "/api/bot/scripts/get", 400),  # Missing data
        ]
        
        all_handled = True
        
        for method, endpoint, expected in test_cases:
            status, body, _ = await self.make_request(method, endpoint)
            
            handled = status == expected or (status >= 400 and status < 600)
            icon = "✅" if handled else "❌"
            print(f"   {icon} {method} {endpoint} → {status} (expected ~{expected})")
            
            if not handled:
                all_handled = False
            
            # Check for stack traces in response (security issue)
            if "at " in body and "node_modules" in body:
                print(f"      ⚠️ Stack trace exposed!")
                all_handled = False
        
        self.results.append(TestResult(
            "Error Handling", 
            all_handled, 
            "Errors handled gracefully"
        ))
    
    async def test_connection_leaks(self):
        """Test for connection leaks"""
        print("\n" + "="*60)
        print("🔗 7. CONNECTION LEAK TESTİ")
        print("="*60)
        
        # Get initial metrics
        status1, body1, _ = await self.make_request("GET", "/api/metrics")
        
        if status1 != 200:
            print(f"   ⚠️ Metrics endpoint not available")
            self.results.append(TestResult("Connection Leak", True, "Metrics not available"))
            return
        
        try:
            metrics1 = json.loads(body1)
            initial_connections = metrics1.get("server", {}).get("connections", 0)
        except:
            initial_connections = 0
        
        print(f"   Initial connections: {initial_connections}")
        
        # Make many requests
        for i in range(50):
            await self.make_request("GET", "/api/health")
        
        await asyncio.sleep(2)  # Wait for connections to close
        
        # Check final metrics
        status2, body2, _ = await self.make_request("GET", "/api/metrics")
        
        try:
            metrics2 = json.loads(body2)
            final_connections = metrics2.get("server", {}).get("connections", 0)
        except:
            final_connections = 0
        
        print(f"   Final connections: {final_connections}")
        
        leak = final_connections > initial_connections + 5
        
        self.results.append(TestResult(
            "Connection Leak", 
            not leak, 
            f"Connections stable: {initial_connections} → {final_connections}"
        ))
    
    async def test_circuit_breaker(self):
        """Test circuit breaker"""
        print("\n" + "="*60)
        print("⚡ 8. CİRCUİT BREAKER TESTİ")
        print("="*60)
        
        status, body, _ = await self.make_request("GET", "/api/metrics")
        
        if status != 200:
            print(f"   ⚠️ Metrics not available")
            self.results.append(TestResult("Circuit Breaker", True, "Not testable"))
            return
        
        try:
            metrics = json.loads(body)
            cb_state = metrics.get("trafficControl", {}).get("circuitBreaker", {}).get("state", "UNKNOWN")
            cb_failures = metrics.get("trafficControl", {}).get("circuitBreaker", {}).get("failures", 0)
            
            print(f"   State: {cb_state}")
            print(f"   Failures: {cb_failures}")
            
            self.results.append(TestResult(
                "Circuit Breaker", 
                cb_state in ["CLOSED", "HALF_OPEN"],
                f"Circuit breaker in {cb_state} state"
            ))
        except Exception as e:
            print(f"   ⚠️ Parse error: {e}")
            self.results.append(TestResult("Circuit Breaker", True, "Unable to parse"))
    
    async def test_concurrent_stress(self):
        """Concurrent stress test"""
        print("\n" + "="*60)
        print("💪 9. EŞZAMANLI STRES TESTİ")
        print("="*60)
        
        num_requests = 200
        
        connector = aiohttp.TCPConnector(limit=100)
        async with aiohttp.ClientSession(connector=connector) as session:
            async def make_req():
                try:
                    async with session.get(f"{self.base_url}/api/health",
                                          timeout=aiohttp.ClientTimeout(total=30)) as resp:
                        await resp.text()
                        return resp.status
                except:
                    return 0
            
            start = time.time()
            results = await asyncio.gather(*[make_req() for _ in range(num_requests)])
            duration = time.time() - start
        
        success = sum(1 for r in results if r == 200)
        rate_limited = sum(1 for r in results if r == 429)
        errors = sum(1 for r in results if r == 0)
        
        rps = num_requests / duration
        
        print(f"   📊 {num_requests} istek / {duration:.2f}s = {rps:.0f} RPS")
        print(f"   ✅ Başarılı: {success}")
        print(f"   🚫 Rate Limited: {rate_limited}")
        print(f"   ❌ Hata: {errors}")
        
        # Server should handle OR rate limit, but not crash
        stable = errors < num_requests * 0.1  # Less than 10% errors
        
        self.results.append(TestResult(
            "Concurrent Stress", 
            stable,
            f"{rps:.0f} RPS, {errors} errors"
        ))
    
    def print_summary(self):
        """Print test summary"""
        print("\n" + "="*70)
        print("📊 TEST SONUÇ ÖZETİ")
        print("="*70)
        
        passed = sum(1 for r in self.results if r.passed)
        total = len(self.results)
        
        for r in self.results:
            icon = "✅" if r.passed else "❌"
            print(f"   {icon} {r.test_name:25} - {r.message}")
        
        print("\n" + "-"*70)
        print(f"   TOPLAM: {passed}/{total} test başarılı")
        
        if passed == total:
            print("\n   🎉 TÜM TESTLER BAŞARILI! Sunucu güvenli ve stabil.")
        else:
            print(f"\n   ⚠️ {total - passed} test başarısız. Yukarıdaki sorunları inceleyin.")
        
        print("="*70)


async def main():
    tester = ComprehensiveTester(API_URL)
    await tester.run_all_tests()


if __name__ == "__main__":
    if sys.platform == 'win32':
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    
    asyncio.run(main())
