# conftest.py - Pytest fixtures and configuration

import pytest
import json
import hashlib
import hmac
import time
import os
from unittest.mock import MagicMock, patch
from datetime import datetime

import psycopg2
from psycopg2.extras import RealDictCursor


# Test configuration
TEST_DB_CONFIG = {
    "host": "postgresql-genesis-u50607.vm.elestio.app",
    "port": 25432,
    "user": "postgres",
    "password": "etY0eog17tD-dDuj--IRH",
    "database": "postgres"
}

TEST_API_KEY = "test-api-key-12345"
TELNYX_API_KEY = "KEY019BE7A3A2D749FCA8681CFF8448A7F0_vTMM1n77CtQxLDT2ra3P1z"
TELNYX_WEBHOOK_SECRET = "test_webhook_secret"


@pytest.fixture(scope="session")
def db_connection():
    """Create database connection for tests."""
    conn = psycopg2.connect(**TEST_DB_CONFIG)
    yield conn
    conn.close()


@pytest.fixture(scope="function")
def db_cursor(db_connection):
    """Create a fresh cursor for each test."""
    cursor = db_connection.cursor(cursor_factory=RealDictCursor)
    yield cursor
    cursor.close()


@pytest.fixture(scope="function")
def clean_tables(db_connection):
    """Clean relevant tables before each test."""
    cursor = db_connection.cursor()
    try:
        cursor.execute("DELETE FROM genesis_bridge.directives")
        cursor.execute("DELETE FROM genesis_bridge.command_queue")
        cursor.execute("DELETE FROM genesis_bridge.call_transcripts")
        db_connection.commit()
    except Exception:
        db_connection.rollback()
    finally:
        cursor.close()
    yield
    # Cleanup after test
    cursor = db_connection.cursor()
    try:
        cursor.execute("DELETE FROM genesis_bridge.directives")
        cursor.execute("DELETE FROM genesis_bridge.command_queue")
        cursor.execute("DELETE FROM genesis_bridge.call_transcripts")
        db_connection.commit()
    except Exception:
        db_connection.rollback()
    finally:
        cursor.close()


@pytest.fixture
def mock_psycopg2():
    """Mock psycopg2 for isolated unit tests."""
    with patch('psycopg2.connect') as mock_conn:
        mock_cursor = MagicMock()
        mock_cursor.__enter__ = MagicMock(return_value=mock_cursor)
        mock_cursor.__exit__ = MagicMock(return_value=False)
        mock_conn.return_value.cursor.return_value = mock_cursor
        mock_conn.return_value.commit = MagicMock()
        yield mock_conn, mock_cursor


@pytest.fixture
def sample_transcript_payload():
    """Sample Telnyx transcription webhook payload."""
    return {
        "data": {
            "id": "call_123456789",
            "type": "call.transcription",
            "event_type": "call.transcription",
            "payload": {
                "call_session_id": "session_abc123",
                "call_id": "call_123456789",
                "from": "+61234567890",
                "to": "+61313004377",
                "transcript": {
                    "text": "Hey Claude, I need you to create a new task for the project",
                    "is_final": True,
                    "confidence": 0.95
                },
                "direction": "inbound",
                "timestamp": "2025-01-15T10:30:00Z"
            }
        },
        "meta": {
            "attempt": 1,
            "timestamp": "2025-01-15T10:30:01Z"
        }
    }


@pytest.fixture
def sample_partial_transcript_payload():
    """Sample partial (streaming) transcription payload."""
    return {
        "data": {
            "id": "call_123456790",
            "type": "call.transcription",
            "event_type": "call.transcription",
            "payload": {
                "call_session_id": "session_abc124",
                "call_id": "call_123456790",
                "from": "+61234567891",
                "to": "+61313004377",
                "transcript": {
                    "text": "Hey Clau",
                    "is_final": False,
                    "confidence": 0.65
                },
                "direction": "inbound",
                "timestamp": "2025-01-15T10:30:05Z"
            }
        },
        "meta": {
            "attempt": 1,
            "timestamp": "2025-01-15T10:30:06Z"
        }
    }


@pytest.fixture
def sample_call_initiated_payload():
    """Sample call.initiated webhook payload."""
    return {
        "data": {
            "id": "call_123456791",
            "type": "call.initiated",
            "event_type": "call.initiated",
            "payload": {
                "call_session_id": "session_abc125",
                "call_id": "call_123456791",
                "from": "+61234567892",
                "to": "+61313004377",
                "direction": "inbound",
                "timestamp": "2025-01-15T10:31:00Z"
            }
        },
        "meta": {
            "attempt": 1,
            "timestamp": "2025-01-15T10:31:01Z"
        }
    }


@pytest.fixture
def sample_call_answered_payload():
    """Sample call.answered webhook payload."""
    return {
        "data": {
            "id": "call_123456792",
            "type": "call.answered",
            "event_type": "call.answered",
            "payload": {
                "call_session_id": "session_abc126",
                "call_id": "call_123456792",
                "from": "+61234567893",
                "to": "+61313004377",
                "direction": "inbound",
                "timestamp": "2025-01-15T10:31:30Z"
            }
        },
        "meta": {
            "attempt": 1,
            "timestamp": "2025-01-15T10:31:31Z"
        }
    }


@pytest.fixture
def sample_call_hangup_payload():
    """Sample call.hangup webhook payload."""
    return {
        "data": {
            "id": "call_123456793",
            "type": "call.hangup",
            "event_type": "call.hangup",
            "payload": {
                "call_session_id": "session_abc127",
                "call_id": "call_123456793",
                "from": "+61234567894",
                "to": "+61313004377",
                "hangup_cause": "normal_clearing",
                "duration": 120,
                "timestamp": "2025-01-15T10:35:00Z"
            }
        },
        "meta": {
            "attempt": 1,
            "timestamp": "2025-01-15T10:35:01Z"
        }
    }


@pytest.fixture
def sample_machine_detection_payload():
    """Sample call.machine.detection webhook payload."""
    return {
        "data": {
            "id": "call_123456794",
            "type": "call.machine.detection",
            "event_type": "call.machine.detection",
            "payload": {
                "call_session_id": "session_abc128",
                "call_id": "call_123456794",
                "from": "+61234567895",
                "to": "+61313004377",
                "machine_detected": True,
                "machine_type": "voicemail",
                "timestamp": "2025-01-15T10:29:00Z"
            }
        },
        "meta": {
            "attempt": 1,
            "timestamp": "2025-01-15T10:29:01Z"
        }
    }


@pytest.fixture
def sample_low_confidence_payload():
    """Sample low confidence transcription payload."""
    return {
        "data": {
            "id": "call_123456795",
            "type": "call.transcription",
            "event_type": "call.transcription",
            "payload": {
                "call_session_id": "session_abc129",
                "call_id": "call_123456795",
                "from": "+61234567896",
                "to": "+61313004377",
                "transcript": {
                    "text": "Um, uh, like, I think maybe, um, Claude can you, uh, do something?",
                    "is_final": True,
                    "confidence": 0.35
                },
                "direction": "inbound",
                "timestamp": "2025-01-15T10:36:00Z"
            }
        },
        "meta": {
            "attempt": 1,
            "timestamp": "2025-01-15T10:36:01Z"
        }
    }


@pytest.fixture
def sample_multi_turn_conversation():
    """Sample multi-turn conversation where directive is spread across utterances."""
    return [
        {
            "data": {
                "id": "call_123456796",
                "type": "call.transcription",
                "event_type": "call.transcription",
                "payload": {
                    "call_session_id": "session_abc130",
                    "call_id": "call_123456796",
                    "from": "+61234567897",
                    "to": "+61313004377",
                    "transcript": {
                        "text": "Hey, I want to talk to Claude",
                        "is_final": True,
                        "confidence": 0.92
                    },
                    "direction": "inbound",
                    "timestamp": "2025-01-15T10:40:00Z"
                }
            },
            "meta": {"attempt": 1, "timestamp": "2025-01-15T10:40:01Z"}
        },
        {
            "data": {
                "id": "call_123456797",
                "type": "call.transcription",
                "event_type": "call.transcription",
                "payload": {
                    "call_session_id": "session_abc130",
                    "call_id": "call_123456796",
                    "from": "+61234567897",
                    "to": "+61313004377",
                    "transcript": {
                        "text": "Directive: please send an email to the team about the meeting",
                        "is_final": True,
                        "confidence": 0.88
                    },
                    "direction": "inbound",
                    "timestamp": "2025-01-15T10:40:30Z"
                }
            },
            "meta": {"attempt": 1, "timestamp": "2025-01-15T10:40:31Z"}
        }
    ]


@pytest.fixture
def sample_non_directive_payload():
    """Sample non-directive conversation payload."""
    return {
        "data": {
            "id": "call_123456798",
            "type": "call.transcription",
            "event_type": "call.transcription",
            "payload": {
                "call_session_id": "session_abc131",
                "call_id": "call_123456798",
                "from": "+61234567898",
                "to": "+61313004377",
                "transcript": {
                    "text": "Hi, I just wanted to check in and see how things are going",
                    "is_final": True,
                    "confidence": 0.90
                },
                "direction": "inbound",
                "timestamp": "2025-01-15T10:45:00Z"
            }
        },
        "meta": {
            "attempt": 1,
            "timestamp": "2025-01-15T10:45:01Z"
        }
    }


def generate_telnyx_signature(payload: dict, secret: str, timestamp: str = None) -> str:
    """Generate Telnyx webhook signature for testing."""
    if timestamp is None:
        timestamp = str(int(time.time()))
    
    payload_str = json.dumps(payload, sort_keys=True)
    signature_data = f"v1:{timestamp}:{payload_str}"
    
    signature = hmac.new(
        secret.encode('utf-8'),
        signature_data.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    
    return f"t={timestamp},v1={signature}"


@pytest.fixture
def valid_webhook_signature(sample_transcript_payload):
    """Generate valid webhook signature."""
    return generate_telnyx_signature(sample_transcript_payload, TELNYX_WEBHOOK_SECRET)


@pytest.fixture
def app():
    """Create FastAPI test application."""
    from fastapi import FastAPI, Header, HTTPException, Request
    from fastapi.testclient import TestClient
    import logging
    
    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger(__name__)
    
    app = FastAPI(title="AIVA Voice Command Bridge")
    
    # Store for conversation context per call
    conversation_context = {}
    
    def verify_telnyx_signature(request: Request, x_telnyx_signature: str = Header(None)):
        """Verify Telnyx webhook signature."""
        if not x_telnyx_signature:
            raise HTTPException(status_code=401, detail="Missing signature")
        
        try:
            parts = dict(p.split('=') for p in x_telnyx_signature.split(','))
            timestamp = parts.get('t', '')
            provided_signature = parts.get('v1', '')
            
            payload_str = json.dumps(await request.json(), sort_keys=True)
            signature_data = f"v1:{timestamp}:{payload_str}"
            
            expected_signature = hmac.new(
                TELNYX_WEBHOOK_SECRET.encode('utf-8'),
                signature_data.encode('utf-8'),
                hashlib.sha256
            ).hexdigest()
            
            if not hmac.compare_digest(expected_signature, provided_signature):
                raise HTTPException(status_code=401, detail="Invalid signature")
        except Exception as e:
            logger.error(f"Signature verification error: {e}")
            raise HTTPException(status_code=401, detail="Invalid signature")
    
    def detect_directive(text: str) -> dict:
        """Detect directive patterns in transcript text."""
        text_lower = text.lower()
        
        keywords = ['claude', 'directive', 'command', 'task', 'task for', 'please']
        command_keywords = ['create', 'send', 'make', 'do', 'get', 'show', 'update', 'delete']
        
        directive_found = False
        directive_type = None
        
        # Check for explicit directive
        if 'directive:' in text_lower:
            directive_found = True
            directive_type = 'explicit'
        elif any(kw in text_lower for kw in ['claude,', 'claude ', 'hey claude']):
            directive_found = True
            directive_type = 'voice_invocation'
        elif 'command:' in text_lower:
            directive_found = True
            directive_type = 'command'
        
        # Extract command details if present
        command = None
        if directive_found:
            for cmd_kw in command_keywords:
                if cmd_kw in text_lower:
                    command = cmd_kw
                    break
        
        return {
            'is_directive': directive_found,
            'directive_type': directive_type,
            'command': command,
            'full_text': text
        }
    
    def store_transcript(db_conn, data: dict) -> int:
        """Store transcript in database."""
        cursor = db_conn.cursor(cursor_factory=RealDictCursor)
        
        call_id = data['payload']['call_id']
        caller_number = data['payload']['from']
        transcript_text = data['payload']['transcript']['text']
        confidence = data['payload']['transcript']['confidence']
        is_final = data['payload']['transcript'].get('is_final', True)
        
        # Determine if this is a directive
        directive_info = detect_directive(transcript_text)
        is_directive = directive_info['is_directive']
        
        # Store the transcript
        cursor.execute("""
            INSERT INTO genesis_bridge.call_transcripts 
            (call_id, caller_number, transcript_text, is_directive, confidence_score, created_at)
            VALUES (%s, %s, %s, %s, %s, NOW())
            RETURNING id
        """, (call_id, caller_number, transcript_text, is_directive, confidence))
        
        result = cursor.fetchone()
        transcript_id = result['id']
        
        db_conn.commit()
        cursor.close()
        
        return transcript_id, is_directive, directive_info
    
    def create_directive_and_queue(db_conn, call_id: str, transcript_id: int, directive_info: dict):
        """Create directive and add to command queue."""
        cursor = db_conn.cursor(cursor_factory=RealDictCursor)
        
        # Insert directive
        cursor.execute("""
            INSERT INTO genesis_bridge.directives 
            (call_id, directive_type, command, raw_text, status, created_at)
            VALUES (%s, %s, %s, %s, %s, NOW())
            RETURNING id
        """, (
            call_id,
            directive_info['directive_type'],
            directive_info['command'],
            directive_info['full_text'],
            'pending'
        ))
        
        directive_result = cursor.fetchone()
        directive_id = directive_result['id']
        
        # Add to command queue
        cursor.execute("""
            INSERT INTO genesis_bridge.command_queue 
            (directive_id, command_type, payload, priority, status, created_at)
            VALUES (%s, %s, %s, %s, %s, NOW())
            RETURNING id
        """, (
            directive_id,
            directive_info['command'] or 'general',
            json.dumps({'call_id': call_id, 'transcript': directive_info['full_text']}),
            'normal',
            'pending'
        ))
        
        queue_result = cursor.fetchone()
        queue_id = queue_result['id']
        
        # Link transcript to directive
        cursor.execute("""
            UPDATE genesis_bridge.call_transcripts 
            SET directive_id = %s
            WHERE id = %s
        """, (directive_id, transcript_id))
        
        db_conn.commit()
        cursor.close()
        
        return directive_id, queue_id
    
    @app.post("/bridge/webhook/telnyx")
    async def telnyx_webhook(
        request: Request,
        x_telnyx_signature: str = Header(None),
        x_api_key: str = Header(None)
    ):
        """Handle Telnyx webhook events."""
        # API key check
        if x_api_key != TEST_API_KEY:
            raise HTTPException(status_code=401, detail="Invalid API key")
        
        # Signature verification
        await verify_telnyx_signature(request, x_telnyx_signature)
        
        data = await request.json()
        event_type = data.get('data', {}).get('event_type')
        
        logger.info(f"Received webhook event: {event_type}")
        
        # Connect to database
        db_conn = psycopg2.connect(**TEST_DB_CONFIG)
        
        try:
            if event_type == 'call.transcription':
                payload = data['payload']
                call_id = payload['call_id']
                transcript_text = payload['transcript']['text']
                confidence = payload['transcript']['confidence']
                is_final = payload['transcript'].get('is_final', True)
                
                # Update conversation context for multi-turn
                if call_id not in conversation_context:
                    conversation_context[call_id] = []
                
                conversation_context[call_id].append(transcript_text)
                
                # Check for directive in this transcript
                directive_info = detect_directive(transcript_text)
                
                # Also check accumulated context
                if not directive_info['is_directive']:
                    full_context = ' '.join(conversation_context[call_id])
                    context_directive = detect_directive(full_context)
                    if context_directive['is_directive']:
                        directive_info = context_directive
                
                # Store transcript
                cursor = db_conn.cursor(cursor_factory=RealDictCursor)
                
                call_id = payload['call_id']
                caller_number = payload['from']
                
                cursor.execute("""
                    INSERT INTO genesis_bridge.call_transcripts 
                    (call_id, caller_number, transcript_text, is_directive, confidence_score, created_at)
                    VALUES (%s, %s, %s, %s, %s, NOW())
                    RETURNING id
                """, (call_id, caller_number, transcript_text, directive_info['is_directive'], confidence))
                
                transcript_id = cursor.fetchone()['id']
                
                # Handle low confidence flagging
                if confidence < 0.5:
                    logger.warning(f"Low confidence transcription ({confidence}) - flagged for review")
                
                # If directive detected, create entries
                if directive_info['is_directive']:
                    # Insert directive
                    cursor.execute("""
                        INSERT INTO genesis_bridge.directives 
                        (call_id, directive_type, command, raw_text, status, created_at)
                        VALUES (%s, %s, %s, %s, %s, NOW())
                        RETURNING id
                    """, (
                        call_id,
                        directive_info['directive_type'],
                        directive_info['command'],
                        directive_info['full_text'],
                        'pending'
                    ))
                    
                    directive_id = cursor.fetchone()['id']
                    
                    # Link transcript to directive
                    cursor.execute("""
                        UPDATE genesis_bridge.call_transcripts 
                        SET directive_id = %s
                        WHERE id = %s
                    """, (directive_id, transcript_id))
                    
                    # Add to command queue
                    cursor.execute("""
                        INSERT INTO genesis_bridge.command_queue 
                        (directive_id, command_type, payload, priority, status, created_at)
                        VALUES (%s, %s, %s, %s, %s, NOW())
                        RETURNING id
                    """, (
                        directive_id,
                        directive_info['command'] or 'general',
                        json.dumps({'call_id': call_id, 'transcript': directive_info['full_text']}),
                        'normal' if confidence >= 0.5 else 'low_priority',
                        'pending'
                    ))
                    
                    logger.info(f"Directive detected and queued: {directive_id}")
                else:
                    logger.info(f"Non-directive conversation stored: {transcript_id}")
                
                db_conn.commit()
                cursor.close()
                
                return {"status": "processed", "event": event_type}
            
            elif event_type == 'call.initiated':
                logger.info(f"Call initiated: {data['data']['id']}")
                return {"status": "acknowledged", "event": event_type}
            
            elif event_type == 'call.answered':
                logger.info(f"Call answered: {data['data']['id']}")
                return {"status": "acknowledged", "event": event_type}
            
            elif event_type == 'call.hangup':
                logger.info(f"Call hung up: {data['data']['id']}")
                # Clean up conversation context
                call_id = data.get('payload', {}).get('call_id')
                if call_id in conversation_context:
                    del conversation_context[call_id]
                return {"status": "acknowledged", "event": event_type}
            
            elif event_type == 'call.machine.detection':
                logger.info(f"Machine detection: {data['data']['id']}")
                return {"status": "acknowledged", "event": event_type}
            
            else:
                logger.warning(f"Unknown event type: {event_type}")
                return {"status": "ignored", "event": event_type}
        
        except Exception as e:
            logger.error(f"Webhook processing error: {e}")
            db_conn.rollback()
            raise HTTPException(status_code=500, detail=str(e))
        finally:
            db_conn.close()
    
    return app


@pytest.fixture
def client(app):
    """Create test client."""
    from fastapi.testclient import TestClient
    return TestClient(app)


@pytest.fixture
def api_headers(valid_webhook_signature):
    """Common API headers for requests."""
    return {
        "Content-Type": "application/json",
        "X-Telnyx-Signature": valid_webhook_signature,
        "X-Api-Key": TEST_API_KEY
    }