"""
PostgreSQL Database Connection and Repository Pattern

Provides async database access using asyncpg with connection pooling.
"""
from __future__ import annotations

import os
from contextlib import asynccontextmanager
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, AsyncIterator
from uuid import UUID

try:
    import asyncpg
    from asyncpg import Pool, Connection
except ImportError:
    asyncpg = None
    Pool = None
    Connection = None

from app.observability.logging import get_logger

_logger = get_logger("postgres")

# Global connection pool
_pool: Optional[Pool] = None


def get_database_url() -> str:
    """Get database URL from environment."""
    return os.getenv(
        "DATABASE_URL",
        "postgresql://postgres:postgres@localhost:5432/sales_model"
    )


async def init_pool(
    min_size: int = 5,
    max_size: int = 20,
    command_timeout: float = 60.0,
) -> Pool:
    """Initialize the connection pool."""
    global _pool
    
    if asyncpg is None:
        raise RuntimeError("asyncpg is not installed. Run: pip install asyncpg")
    
    if _pool is not None:
        return _pool
    
    database_url = get_database_url()
    
    _logger.info("initializing_postgres_pool", min_size=min_size, max_size=max_size)
    
    _pool = await asyncpg.create_pool(
        database_url,
        min_size=min_size,
        max_size=max_size,
        command_timeout=command_timeout,
        server_settings={
            'timezone': 'UTC',
        },
    )
    
    _logger.info("postgres_pool_initialized")
    return _pool


async def close_pool() -> None:
    """Close the connection pool."""
    global _pool
    
    if _pool is not None:
        _logger.info("closing_postgres_pool")
        await _pool.close()
        _pool = None


async def get_pool() -> Pool:
    """Get the connection pool, initializing if needed."""
    global _pool
    
    if _pool is None:
        await init_pool()
    
    return _pool


@asynccontextmanager
async def get_connection() -> AsyncIterator[Connection]:
    """Get a database connection from the pool."""
    pool = await get_pool()
    async with pool.acquire() as conn:
        yield conn


@asynccontextmanager
async def get_transaction() -> AsyncIterator[Connection]:
    """Get a database connection with transaction."""
    pool = await get_pool()
    async with pool.acquire() as conn:
        async with conn.transaction():
            yield conn


# =============================================================================
# User Repository
# =============================================================================

class UserRepository:
    """Repository for user operations."""
    
    @staticmethod
    async def create(
        email: str,
        password_hash: str,
        first_name: str,
        last_name: str,
        role: str = "member",
        organization_id: Optional[UUID] = None,
    ) -> Dict[str, Any]:
        """Create a new user."""
        async with get_connection() as conn:
            row = await conn.fetchrow(
                """
                INSERT INTO users (email, password_hash, first_name, last_name, role, organization_id)
                VALUES ($1, $2, $3, $4, $5, $6)
                RETURNING id, email, first_name, last_name, role, organization_id, 
                          avatar_url, is_active, last_login_at, created_at, updated_at
                """,
                email.lower(),
                password_hash,
                first_name,
                last_name,
                role,
                organization_id,
            )
            return dict(row) if row else None
    
    @staticmethod
    async def get_by_id(user_id: UUID) -> Optional[Dict[str, Any]]:
        """Get user by ID."""
        async with get_connection() as conn:
            row = await conn.fetchrow(
                """
                SELECT id, email, password_hash, first_name, last_name, role, 
                       organization_id, avatar_url, is_active, last_login_at, 
                       created_at, updated_at
                FROM users WHERE id = $1 AND is_active = TRUE
                """,
                user_id,
            )
            return dict(row) if row else None
    
    @staticmethod
    async def get_by_email(email: str) -> Optional[Dict[str, Any]]:
        """Get user by email."""
        async with get_connection() as conn:
            row = await conn.fetchrow(
                """
                SELECT id, email, password_hash, first_name, last_name, role, 
                       organization_id, avatar_url, is_active, last_login_at, 
                       created_at, updated_at
                FROM users WHERE email = $1 AND is_active = TRUE
                """,
                email.lower(),
            )
            return dict(row) if row else None
    
    @staticmethod
    async def update_last_login(user_id: UUID) -> None:
        """Update user's last login timestamp."""
        async with get_connection() as conn:
            await conn.execute(
                "UPDATE users SET last_login_at = NOW() WHERE id = $1",
                user_id,
            )
    
    @staticmethod
    async def update_profile(
        user_id: UUID,
        first_name: Optional[str] = None,
        last_name: Optional[str] = None,
        avatar_url: Optional[str] = None,
    ) -> bool:
        """Update user profile."""
        updates = []
        values = []
        idx = 1
        
        if first_name is not None:
            updates.append(f"first_name = ${idx}")
            values.append(first_name)
            idx += 1
        if last_name is not None:
            updates.append(f"last_name = ${idx}")
            values.append(last_name)
            idx += 1
        if avatar_url is not None:
            updates.append(f"avatar_url = ${idx}")
            values.append(avatar_url)
            idx += 1
        
        if not updates:
            return True
        
        values.append(user_id)
        query = f"UPDATE users SET {', '.join(updates)} WHERE id = ${idx}"
        
        async with get_connection() as conn:
            result = await conn.execute(query, *values)
            return result == "UPDATE 1"
    
    @staticmethod
    async def update_password(user_id: UUID, password_hash: str) -> bool:
        """Update user password."""
        async with get_connection() as conn:
            result = await conn.execute(
                "UPDATE users SET password_hash = $1 WHERE id = $2",
                password_hash,
                user_id,
            )
            return result == "UPDATE 1"
    
    @staticmethod
    async def set_organization(user_id: UUID, organization_id: UUID, role: str = "owner") -> bool:
        """Set user's organization."""
        async with get_connection() as conn:
            result = await conn.execute(
                "UPDATE users SET organization_id = $1, role = $2 WHERE id = $3",
                organization_id,
                role,
                user_id,
            )
            return result == "UPDATE 1"


# =============================================================================
# Organization Repository
# =============================================================================

class OrganizationRepository:
    """Repository for organization operations."""
    
    @staticmethod
    async def create(
        name: str,
        owner_id: UUID,
        industry: str = "other",
        size: str = "1-10",
        country: str = "US",
        website: Optional[str] = None,
        description: Optional[str] = None,
        plan: str = "free",
    ) -> Dict[str, Any]:
        """Create a new organization."""
        async with get_transaction() as conn:
            row = await conn.fetchrow(
                """
                INSERT INTO organizations (name, owner_id, industry, size, country, website, description, plan)
                VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
                RETURNING id, name, owner_id, industry, size, country, website, description, 
                          plan, is_active, created_at, updated_at
                """,
                name,
                owner_id,
                industry,
                size,
                country,
                website,
                description,
                plan,
            )
            
            if row:
                # Update user's organization
                await conn.execute(
                    "UPDATE users SET organization_id = $1, role = 'owner' WHERE id = $2",
                    row["id"],
                    owner_id,
                )
            
            return dict(row) if row else None
    
    @staticmethod
    async def get_by_id(org_id: UUID) -> Optional[Dict[str, Any]]:
        """Get organization by ID with member count."""
        async with get_connection() as conn:
            row = await conn.fetchrow(
                """
                SELECT o.*, 
                       (SELECT COUNT(*) FROM users WHERE organization_id = o.id AND is_active = TRUE) as member_count
                FROM organizations o
                WHERE o.id = $1 AND o.is_active = TRUE
                """,
                org_id,
            )
            return dict(row) if row else None
    
    @staticmethod
    async def get_by_user_id(user_id: UUID) -> Optional[Dict[str, Any]]:
        """Get organization for a user."""
        async with get_connection() as conn:
            row = await conn.fetchrow(
                """
                SELECT o.*, 
                       (SELECT COUNT(*) FROM users WHERE organization_id = o.id AND is_active = TRUE) as member_count
                FROM organizations o
                JOIN users u ON u.organization_id = o.id
                WHERE u.id = $1 AND o.is_active = TRUE AND u.is_active = TRUE
                """,
                user_id,
            )
            return dict(row) if row else None
    
    @staticmethod
    async def update(
        org_id: UUID,
        name: Optional[str] = None,
        industry: Optional[str] = None,
        size: Optional[str] = None,
        country: Optional[str] = None,
        website: Optional[str] = None,
        description: Optional[str] = None,
    ) -> bool:
        """Update organization."""
        updates = []
        values = []
        idx = 1
        
        for field, value in [
            ("name", name), ("industry", industry), ("size", size),
            ("country", country), ("website", website), ("description", description)
        ]:
            if value is not None:
                updates.append(f"{field} = ${idx}")
                values.append(value)
                idx += 1
        
        if not updates:
            return True
        
        values.append(org_id)
        query = f"UPDATE organizations SET {', '.join(updates)} WHERE id = ${idx}"
        
        async with get_connection() as conn:
            result = await conn.execute(query, *values)
            return result == "UPDATE 1"
    
    @staticmethod
    async def update_plan(org_id: UUID, plan: str) -> bool:
        """Update organization plan."""
        async with get_connection() as conn:
            result = await conn.execute(
                "UPDATE organizations SET plan = $1 WHERE id = $2",
                plan,
                org_id,
            )
            return result == "UPDATE 1"


# =============================================================================
# API Key Repository
# =============================================================================

class ApiKeyRepository:
    """Repository for API key operations."""
    
    @staticmethod
    async def create(
        name: str,
        key_hash: str,
        key_prefix: str,
        organization_id: UUID,
        created_by: UUID,
        scopes: List[str],
        expires_at: Optional[datetime] = None,
    ) -> Dict[str, Any]:
        """Create a new API key."""
        async with get_connection() as conn:
            row = await conn.fetchrow(
                """
                INSERT INTO api_keys (name, key_hash, key_prefix, organization_id, created_by, scopes, expires_at)
                VALUES ($1, $2, $3, $4, $5, $6, $7)
                RETURNING id, name, key_prefix, organization_id, created_by, scopes,
                          is_active, last_used_at, usage_count, created_at
                """,
                name,
                key_hash,
                key_prefix,
                organization_id,
                created_by,
                scopes,
                expires_at,
            )
            return dict(row) if row else None
    
    @staticmethod
    async def get_by_hash(key_hash: str) -> Optional[Dict[str, Any]]:
        """Get API key by hash."""
        async with get_connection() as conn:
            row = await conn.fetchrow(
                """
                SELECT ak.*, o.plan as organization_plan
                FROM api_keys ak
                JOIN organizations o ON o.id = ak.organization_id
                WHERE ak.key_hash = $1 AND ak.is_active = TRUE 
                  AND (ak.expires_at IS NULL OR ak.expires_at > NOW())
                """,
                key_hash,
            )
            return dict(row) if row else None
    
    @staticmethod
    async def get_by_organization(org_id: UUID) -> List[Dict[str, Any]]:
        """Get all API keys for an organization."""
        async with get_connection() as conn:
            rows = await conn.fetch(
                """
                SELECT id, name, key_prefix, organization_id, created_by, scopes,
                       is_active, last_used_at, usage_count, created_at
                FROM api_keys
                WHERE organization_id = $1
                ORDER BY created_at DESC
                """,
                org_id,
            )
            return [dict(row) for row in rows]
    
    @staticmethod
    async def revoke(key_id: UUID, revoked_by: UUID) -> bool:
        """Revoke an API key."""
        async with get_connection() as conn:
            result = await conn.execute(
                """
                UPDATE api_keys 
                SET is_active = FALSE, revoked_at = NOW(), revoked_by = $1
                WHERE id = $2
                """,
                revoked_by,
                key_id,
            )
            return result == "UPDATE 1"
    
    @staticmethod
    async def increment_usage(key_id: UUID) -> None:
        """Increment API key usage count."""
        async with get_connection() as conn:
            await conn.execute(
                "SELECT increment_api_key_usage($1)",
                key_id,
            )


# =============================================================================
# Activity Repository
# =============================================================================

class ActivityRepository:
    """Repository for activity log operations."""
    
    @staticmethod
    async def create(
        organization_id: UUID,
        activity_type: str,
        title: str,
        description: Optional[str] = None,
        user_id: Optional[UUID] = None,
        metadata: Optional[Dict[str, Any]] = None,
        ip_address: Optional[str] = None,
        user_agent: Optional[str] = None,
    ) -> Dict[str, Any]:
        """Create an activity log entry."""
        async with get_connection() as conn:
            row = await conn.fetchrow(
                """
                INSERT INTO activities (organization_id, user_id, activity_type, title, description, metadata, ip_address, user_agent)
                VALUES ($1, $2, $3, $4, $5, $6, $7::inet, $8)
                RETURNING id, organization_id, user_id, activity_type, title, description, metadata, created_at
                """,
                organization_id,
                user_id,
                activity_type,
                title,
                description,
                metadata or {},
                ip_address,
                user_agent,
            )
            return dict(row) if row else None
    
    @staticmethod
    async def get_by_organization(
        org_id: UUID,
        limit: int = 20,
        offset: int = 0,
    ) -> List[Dict[str, Any]]:
        """Get activities for an organization."""
        async with get_connection() as conn:
            rows = await conn.fetch(
                """
                SELECT a.*, u.first_name, u.last_name,
                       CONCAT(u.first_name, ' ', u.last_name) as user_name
                FROM activities a
                LEFT JOIN users u ON u.id = a.user_id
                WHERE a.organization_id = $1
                ORDER BY a.created_at DESC
                LIMIT $2 OFFSET $3
                """,
                org_id,
                limit,
                offset,
            )
            return [dict(row) for row in rows]


# =============================================================================
# Usage Metrics Repository
# =============================================================================

class UsageRepository:
    """Repository for usage metrics."""
    
    @staticmethod
    async def record_request(
        organization_id: UUID,
        endpoint: str,
        method: str,
        status_code: int,
        response_time_ms: int,
        api_key_id: Optional[UUID] = None,
        request_size: Optional[int] = None,
        response_size: Optional[int] = None,
        error_message: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> None:
        """Record a usage metric."""
        async with get_connection() as conn:
            await conn.execute(
                """
                INSERT INTO usage_metrics 
                (organization_id, api_key_id, endpoint, method, status_code, 
                 response_time_ms, request_size_bytes, response_size_bytes, error_message, metadata)
                VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
                """,
                organization_id,
                api_key_id,
                endpoint,
                method,
                status_code,
                response_time_ms,
                request_size,
                response_size,
                error_message,
                metadata or {},
            )
    
    @staticmethod
    async def get_daily_stats(
        org_id: UUID,
        days: int = 30,
    ) -> List[Dict[str, Any]]:
        """Get daily usage statistics."""
        async with get_connection() as conn:
            rows = await conn.fetch(
                """
                SELECT date, total_requests, successful_requests, failed_requests,
                       avg_response_time_ms, voice_sessions, voice_minutes
                FROM daily_usage_aggregates
                WHERE organization_id = $1 AND date >= CURRENT_DATE - $2::integer
                ORDER BY date ASC
                """,
                org_id,
                days,
            )
            return [dict(row) for row in rows]
    
    @staticmethod
    async def get_monthly_summary(org_id: UUID) -> Dict[str, Any]:
        """Get current month usage summary."""
        async with get_connection() as conn:
            row = await conn.fetchrow(
                """
                SELECT 
                    COALESCE(SUM(total_requests), 0) as total_requests,
                    COALESCE(SUM(successful_requests), 0) as successful_requests,
                    COALESCE(SUM(failed_requests), 0) as failed_requests,
                    COALESCE(AVG(avg_response_time_ms), 0) as avg_response_time_ms,
                    COALESCE(SUM(voice_sessions), 0) as voice_sessions,
                    COALESCE(SUM(voice_minutes), 0) as voice_minutes
                FROM daily_usage_aggregates
                WHERE organization_id = $1 AND date >= DATE_TRUNC('month', CURRENT_DATE)
                """,
                org_id,
            )
            return dict(row) if row else {}


# =============================================================================
# Voice Session Repository
# =============================================================================

class VoiceSessionRepository:
    """Repository for voice session operations."""
    
    @staticmethod
    async def create(
        sid: str,
        organization_id: UUID,
        api_key_id: Optional[UUID] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """Create a new voice session."""
        async with get_connection() as conn:
            row = await conn.fetchrow(
                """
                INSERT INTO voice_sessions (sid, organization_id, api_key_id, metadata)
                VALUES ($1, $2, $3, $4)
                RETURNING id, sid, organization_id, status, started_at
                """,
                sid,
                organization_id,
                api_key_id,
                metadata or {},
            )
            return dict(row) if row else None
    
    @staticmethod
    async def complete(
        sid: str,
        duration_seconds: int,
        audio_minutes: float,
        transcript_words: Optional[int] = None,
        lead_score: Optional[int] = None,
        call_outcome: Optional[str] = None,
    ) -> bool:
        """Mark a voice session as completed."""
        async with get_connection() as conn:
            result = await conn.execute(
                """
                UPDATE voice_sessions
                SET status = 'completed', ended_at = NOW(),
                    duration_seconds = $1, audio_minutes = $2,
                    transcript_words = $3, lead_score = $4, call_outcome = $5
                WHERE sid = $6
                """,
                duration_seconds,
                audio_minutes,
                transcript_words,
                lead_score,
                call_outcome,
                sid,
            )
            return result == "UPDATE 1"
    
    @staticmethod
    async def get_by_organization(
        org_id: UUID,
        limit: int = 50,
        offset: int = 0,
    ) -> List[Dict[str, Any]]:
        """Get voice sessions for an organization."""
        async with get_connection() as conn:
            rows = await conn.fetch(
                """
                SELECT id, sid, status, duration_seconds, audio_minutes,
                       transcript_words, lead_score, call_outcome,
                       started_at, ended_at
                FROM voice_sessions
                WHERE organization_id = $1
                ORDER BY started_at DESC
                LIMIT $2 OFFSET $3
                """,
                org_id,
                limit,
                offset,
            )
            return [dict(row) for row in rows]


# =============================================================================
# User Settings Repository
# =============================================================================

class UserSettingsRepository:
    """Repository for user settings."""
    
    @staticmethod
    async def get(user_id: UUID) -> Optional[Dict[str, Any]]:
        """Get user settings."""
        async with get_connection() as conn:
            row = await conn.fetchrow(
                "SELECT * FROM user_settings WHERE user_id = $1",
                user_id,
            )
            return dict(row) if row else None
    
    @staticmethod
    async def upsert(
        user_id: UUID,
        email_notifications: bool = True,
        usage_alerts: bool = True,
        security_alerts: bool = True,
        billing_alerts: bool = True,
        two_factor_enabled: bool = False,
        session_timeout_minutes: int = 480,
        timezone: str = "UTC",
    ) -> Dict[str, Any]:
        """Create or update user settings."""
        async with get_connection() as conn:
            row = await conn.fetchrow(
                """
                INSERT INTO user_settings 
                (user_id, email_notifications, usage_alerts, security_alerts, 
                 billing_alerts, two_factor_enabled, session_timeout_minutes, timezone)
                VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
                ON CONFLICT (user_id) DO UPDATE SET
                    email_notifications = EXCLUDED.email_notifications,
                    usage_alerts = EXCLUDED.usage_alerts,
                    security_alerts = EXCLUDED.security_alerts,
                    billing_alerts = EXCLUDED.billing_alerts,
                    two_factor_enabled = EXCLUDED.two_factor_enabled,
                    session_timeout_minutes = EXCLUDED.session_timeout_minutes,
                    timezone = EXCLUDED.timezone,
                    updated_at = NOW()
                RETURNING *
                """,
                user_id,
                email_notifications,
                usage_alerts,
                security_alerts,
                billing_alerts,
                two_factor_enabled,
                session_timeout_minutes,
                timezone,
            )
            return dict(row) if row else None


# =============================================================================
# Helper for Voice Sessions (backwards compatibility)
# =============================================================================

@dataclass
class VoiceSessionRecord:
    sid: str
    user_id: str
    org_id: Optional[str]
    plan: str


async def save_session(record: VoiceSessionRecord) -> None:
    """Save voice session (backwards compatibility wrapper)."""
    if record.org_id:
        from uuid import UUID as PyUUID
        await VoiceSessionRepository.create(
            sid=record.sid,
            organization_id=PyUUID(record.org_id),
        )
