"""
Pytest fixtures for E2E testing of Core modules.
Provides an authenticated client that goes through the full user journey.
"""
import asyncio
import httpx
import pytest
import pytest_asyncio
import uuid
import re
import logging
import time
from typing import AsyncGenerator, Optional, List, Dict
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.exc import DBAPIError
from app.db.session import AsyncSessionLocal

logger = logging.getLogger(__name__)

# Configuration
BASE_URL = "http://sf_api:8000"
MAILPIT_URL = "http://sf_mailpit:8025"
TEST_EMAIL_DOMAIN = "example.com"


class MailpitClient:
    """Client for interacting with Mailpit API."""
    
    def __init__(self, base_url: str = MAILPIT_URL):
        self.base_url = base_url
        self.client = httpx.AsyncClient(timeout=30.0)
    
    async def delete_all_messages(self) -> bool:
        """Delete all messages in Mailpit to ensure clean state."""
        try:
            response = await self.client.delete(f"{self.base_url}/api/v1/messages")
            response.raise_for_status()
            logger.debug("Mailpit cleaned (all messages deleted).")
            return True
        except Exception as e:
            logger.warning(f"Mailpit clean failed: {e}, continuing anyway.")
            return False
    
    async def get_messages(self, limit: int = 50) -> Optional[Dict]:
        """Fetch messages from Mailpit."""
        try:
            response = await self.client.get(f"{self.base_url}/api/v1/messages?limit={limit}")
            response.raise_for_status()
            return response.json()
        except Exception as e:
            logger.error(f"Failed to fetch messages: {e}")
            return None
    
    async def get_latest_message(self) -> Optional[dict]:
        """Fetch the latest email message from Mailpit."""
        data = await self.get_messages(limit=1)
        if data and data.get("messages"):
            return data["messages"][0]
        return None
    
    async def get_message_content(self, message_id: str) -> Optional[str]:
        """Get the full content (HTML and text) of a specific message."""
        try:
            response = await self.client.get(f"{self.base_url}/api/v1/message/{message_id}")
            response.raise_for_status()
            data = response.json()
            # Prefer text over HTML
            return data.get("Text") or data.get("HTML") or ""
        except Exception as e:
            logger.error(f"Failed to fetch message content: {e}")
            return None
    
    async def poll_for_verification_email(self, email: str, max_attempts: int = 5, wait_seconds: int = 3) -> Optional[str]:
        """
        Poll Mailpit for a verification email sent to the given email address.
        Returns the verification token if found, otherwise None.
        """
        for attempt in range(1, max_attempts + 1):
            logger.debug(f"Polling for verification email (attempt {attempt}/{max_attempts})...")
            data = await self.get_messages(limit=20)
            if not data or not data.get("messages"):
                logger.debug(f"No emails in Mailpit, waiting {wait_seconds}s...")
                await asyncio.sleep(wait_seconds)
                continue
            
            # Search for email sent to the target address
            for msg in data.get("messages", []):
                to_list = msg.get("To", [])
                email_found = False
                for recipient in to_list:
                    if isinstance(recipient, dict) and recipient.get("Address") == email:
                        email_found = True
                        break
                    elif isinstance(recipient, str) and recipient == email:
                        email_found = True
                        break
                
                if email_found:
                    msg_id = msg.get("ID")
                    if not msg_id:
                        continue
                    content = await self.get_message_content(msg_id)
                    if content:
                        token = extract_verification_token(content)
                        if token:
                            logger.debug(f"Token found on attempt {attempt}: {token}")
                            return token
                        else:
                            logger.debug(f"Email found but no token pattern matched.")
                    else:
                        logger.debug(f"Could not fetch email content.")
            
            logger.debug(f"No verification email found yet, waiting {wait_seconds}s...")
            await asyncio.sleep(wait_seconds)
        
        logger.error(f"Could not retrieve verification token after {max_attempts} attempts.")
        return None
    
    async def cleanup(self):
        """Close the HTTP client."""
        await self.client.aclose()


class APIClient:
    """Client for interacting with the Service Finder API."""
    
    def __init__(self, base_url: str = BASE_URL):
        self.base_url = base_url
        self.client = httpx.AsyncClient(timeout=30.0)
        self.token = None
    
    async def register(self, email: str, password: str = "TestPassword123!") -> httpx.Response:
        """Register a new user."""
        payload = {
            "email": email,
            "password": password,
            "first_name": "Test",
            "last_name": "User",
            "region_code": "HU",
            "lang": "hu"
        }
        response = await self.client.post(f"{self.base_url}/api/v1/auth/register", json=payload)
        return response
    
    async def login(self, email: str, password: str = "TestPassword123!") -> Optional[str]:
        """Login and return JWT token."""
        payload = {
            "username": email,
            "password": password
        }
        response = await self.client.post(f"{self.base_url}/api/v1/auth/login", data=payload)
        if response.status_code == 200:
            data = response.json()
            self.token = data.get("access_token")
            return self.token
        return None
    
    async def verify_email(self, token: str) -> httpx.Response:
        """Verify email with token."""
        response = await self.client.post(
            f"{self.base_url}/api/v1/auth/verify-email",
            json={"token": token}
        )
        return response
    
    async def complete_kyc(self, token: str) -> httpx.Response:
        """Complete KYC with dummy data (matching Sandbox script)."""
        payload = {
            "phone_number": "+36123456789",
            "birth_place": "Budapest",
            "birth_date": "1990-01-01",
            "mothers_last_name": "Kovács",
            "mothers_first_name": "Éva",
            "address_zip": "1051",
            "address_city": "Budapest",
            "address_street_name": "Váci",
            "address_street_type": "utca",
            "address_house_number": "1",
            "address_stairwell": None,
            "address_floor": None,
            "address_door": None,
            "address_hrsz": None,
            "identity_docs": {
                "ID_CARD": {
                    "number": "123456AB",
                    "expiry_date": "2030-12-31"
                }
            },
            "ice_contact": {
                "name": "John Doe",
                "phone": "+36198765432",
                "relationship": "friend"
            },
            "preferred_language": "hu",
            "preferred_currency": "HUF"
        }
        headers = {"Authorization": f"Bearer {token}"}
        response = await self.client.post(
            f"{self.base_url}/api/v1/auth/complete-kyc",
            json=payload,
            headers=headers
        )
        return response
    
    async def get_authenticated_client(self, token: str) -> httpx.AsyncClient:
        """Return a new httpx.AsyncClient with Authorization header set."""
        headers = {"Authorization": f"Bearer {token}"}
        return httpx.AsyncClient(base_url=self.base_url, headers=headers, timeout=30.0)
    
    async def cleanup(self):
        """Close the HTTP client."""
        await self.client.aclose()


def extract_verification_token(email_content: str) -> Optional[str]:
    """Extract verification token from email content."""
    # Look for token in URL patterns
    patterns = [
        r"token=([a-zA-Z0-9\-_]+)",
        r"/verify/([a-zA-Z0-9\-_]+)",
        r"verification code: ([a-zA-Z0-9\-_]+)",
    ]
    for pattern in patterns:
        match = re.search(pattern, email_content)
        if match:
            return match.group(1)
    return None


@pytest.fixture(scope="session")
def event_loop():
    """Create an instance of the default event loop for the test session."""
    loop = asyncio.get_event_loop_policy().new_event_loop()
    yield loop
    loop.close()


@pytest_asyncio.fixture(scope="function")
async def db() -> AsyncGenerator[AsyncSession, None]:
    """
    Database session fixture with automatic rollback after each test.
    This prevents InFailedSQLTransactionError between tests.
    """
    async with AsyncSessionLocal() as session:
        yield session
        # EZ A KULCS: Minden teszt után takarítunk!
        try:
            await session.rollback()
        except DBAPIError as e:
            logger.warning(f"Rollback failed (likely already aborted): {e}. Closing session.")
            await session.close()
        # Opcionálisan: await session.close()


@pytest_asyncio.fixture(scope="function")
async def authenticated_client() -> AsyncGenerator[httpx.AsyncClient, None]:
    """
    Fixture that performs full user journey and returns an authenticated httpx.AsyncClient.
    
    Steps:
    1. Clean Mailpit to ensure only new emails
    2. Register a new user with unique email (time-based to avoid duplicate key)
    3. Poll Mailpit for verification email and extract token
    4. Verify email
    5. Login to get JWT token
    6. Complete KYC
    7. Return authenticated client with Authorization header
    """
    # Generate unique email using timestamp to avoid duplicate key errors in user_stats
    unique_id = int(time.time() * 1000)  # milliseconds
    email = f"test_{unique_id}@{TEST_EMAIL_DOMAIN}"
    password = "TestPassword123!"
    
    api_client = APIClient()
    mailpit = MailpitClient()
    
    try:
        # 0. Clean Mailpit before registration
        logger.debug("Cleaning Mailpit before registration...")
        await mailpit.delete_all_messages()
        
        # 1. Register
        logger.debug(f"Registering user with email: {email}")
        reg_response = await api_client.register(email, password)
        assert reg_response.status_code in (200, 201), f"Registration failed: {reg_response.text}"
        
        # 2. Poll for verification email and extract token
        logger.debug("Polling Mailpit for verification email...")
        token = await mailpit.poll_for_verification_email(email, max_attempts=5, wait_seconds=3)
        assert token is not None, "Could not retrieve verification token after polling"
        
        # 3. Verify email
        verify_response = await api_client.verify_email(token)
        assert verify_response.status_code == 200, f"Email verification failed: {verify_response.text}"
        
        # 4. Login
        access_token = await api_client.login(email, password)
        assert access_token is not None, "Login failed"
        
        # 5. Complete KYC (optional, log failure but continue)
        kyc_response = await api_client.complete_kyc(access_token)
        if kyc_response.status_code != 200:
            logger.warning(f"KYC completion returned {kyc_response.status_code}: {kyc_response.text}. Continuing anyway.")
        
        # 6. Create authenticated client
        auth_client = await api_client.get_authenticated_client(access_token)
        yield auth_client
        
        # Cleanup
        await auth_client.aclose()
        
    finally:
        await api_client.cleanup()
        await mailpit.cleanup()


@pytest_asyncio.fixture(scope="function")
async def setup_organization(authenticated_client):
    """Létrehoz egy céget a jármű/költség tesztekhez."""
    import time
    import random
    unique_id = int(time.time() * 1000) + random.randint(1, 9999)
    # Generate a valid Hungarian tax number format: 8 digits + "-1-42"
    tax_prefix = random.randint(10000000, 99999999)
    payload = {
        "name": f"Test Fleet {unique_id}",
        "display_name": f"Test Fleet {unique_id}",
        "full_name": f"Test Fleet Kft. {unique_id}",
        "tax_number": f"{tax_prefix}-1-42",
        "registration_number": f"01-09-{unique_id}"[:6],
        "org_type": "business",
        "country_code": "HU",
        "address_zip": "1051",
        "address_city": "Budapest",
        "address_street_name": "Váci",
        "address_street_type": "utca",
        "address_house_number": "1",
        "address_stairwell": None,
        "address_floor": None,
        "address_door": None,
        "address_hrsz": None,
    }
    response = await authenticated_client.post("/api/v1/organizations/onboard", json=payload)
    # Accept 409 as well (already exists) and try to fetch existing organization
    if response.status_code == 409:
        # Maybe we can reuse the existing organization? For simplicity, we'll just skip and raise.
        # But we need an organization ID. Let's try to get the user's organizations.
        # For now, raise a specific error.
        raise ValueError(f"Organization with tax number already exists: {payload['tax_number']}")
    assert response.status_code in (200, 201), f"Organization creation failed: {response.text}"
    data = response.json()
    # Try multiple possible keys for organization ID
    if "id" in data:
        return data["id"]
    elif "organization_id" in data:
        return data["organization_id"]
    elif "organization" in data and "id" in data["organization"]:
        return data["organization"]["id"]
    else:
        # Fallback: extract from location header or raise
        raise ValueError(f"Could not find organization ID in response: {data}")

@pytest_asyncio.fixture
async def setup_vehicle(authenticated_client, setup_organization):
    import time
    unique_vin = f"WBA0000000{int(time.time())}"[:17].ljust(17, '0')
    payload = {
        "vin": unique_vin,
        "license_plate": "TEST-123",
        "organization_id": setup_organization,
        "purchase_price_net": 10000000,
        "purchase_date": "2023-01-01",
        "initial_mileage": 10000,
        "fuel_type": "petrol",
        "transmission": "manual"
    }
    response = await authenticated_client.post("/api/v1/assets/vehicles", json=payload)
    assert response.status_code in (200, 201), f"Vehicle creation failed: {response.text}"
    return response.json()["id"]