"""Tenant management service."""

import hashlib
import json
import secrets
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any


@dataclass
class Tenant:
    """A tenant (client/organization) on the platform."""

    id: str
    name: str
    api_key: str  # Only available at creation time
    default_model: str = "openai:gpt-4o"
    rate_limit_per_minute: int = 60
    monthly_token_limit: int | None = None
    is_active: bool = True
    metadata: dict[str, Any] = field(default_factory=dict)
    created_at: datetime | None = None


@dataclass
class ApiKey:
    """An API key for a tenant."""

    id: str
    tenant_id: str
    key_prefix: str
    name: str | None = None
    scopes: list[str] = field(default_factory=lambda: ["chat"])
    rate_limit_override: int | None = None
    is_active: bool = True
    last_used_at: datetime | None = None
    expires_at: datetime | None = None


class TenantService:
    """Service for managing tenants and API keys.

    Example:
        ```python
        service = TenantService(db)

        # Create a new tenant
        tenant = await service.create_tenant(
            name="Acme Corp",
            default_model="openai:gpt-4o-mini",
            rate_limit_per_minute=100,
        )
        print(f"API Key: {tenant.api_key}")  # Only shown once!

        # Look up tenant by API key
        tenant = await service.get_tenant_by_api_key("sk-tenant-xxx")
        ```
    """

    def __init__(self, db: Any) -> None:
        self.db = db

    @staticmethod
    def generate_api_key() -> tuple[str, str]:
        """Generate a new API key and its hash.

        Returns:
            Tuple of (api_key, key_hash).
        """
        key = f"sk-tenant-{secrets.token_urlsafe(32)}"
        key_hash = hashlib.sha256(key.encode()).hexdigest()
        return key, key_hash

    @staticmethod
    def hash_api_key(api_key: str) -> str:
        """Hash an API key for lookup."""
        return hashlib.sha256(api_key.encode()).hexdigest()

    async def create_tenant(
        self,
        name: str,
        default_model: str = "openai:gpt-4o",
        rate_limit_per_minute: int = 60,
        monthly_token_limit: int | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> Tenant:
        """Create a new tenant.

        Returns the tenant with the API key (only available at creation).
        """
        import uuid

        tenant_id = f"tenant_{uuid.uuid4().hex[:12]}"
        api_key, key_hash = self.generate_api_key()

        await self.db.execute(
            """
            INSERT INTO tenants (id, name, api_key, api_key_hash, default_model,
                                 rate_limit_per_minute, monthly_token_limit, metadata)
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
            """,
            tenant_id,
            name,
            api_key[:16] + "...",  # Store truncated for display
            key_hash,
            default_model,
            rate_limit_per_minute,
            monthly_token_limit,
            json.dumps(metadata or {}),
        )

        return Tenant(
            id=tenant_id,
            name=name,
            api_key=api_key,  # Full key only returned at creation
            default_model=default_model,
            rate_limit_per_minute=rate_limit_per_minute,
            monthly_token_limit=monthly_token_limit,
            metadata=metadata or {},
        )

    async def get_tenant_by_api_key(self, api_key: str) -> Tenant | None:
        """Look up a tenant by API key.

        Returns None if not found or inactive.
        """
        key_hash = self.hash_api_key(api_key)

        result = await self.db.fetch_one(
            """
            SELECT id, name, default_model, rate_limit_per_minute,
                   monthly_token_limit, is_active, metadata, created_at
            FROM tenants
            WHERE api_key_hash = $1 AND is_active = true
            """,
            key_hash,
        )

        if not result:
            return None

        return Tenant(
            id=result["id"],
            name=result["name"],
            api_key="",  # Not returned on lookup
            default_model=result["default_model"],
            rate_limit_per_minute=result["rate_limit_per_minute"],
            monthly_token_limit=result["monthly_token_limit"],
            is_active=result["is_active"],
            metadata=result["metadata"] or {},
            created_at=result["created_at"],
        )

    async def get_tenant(self, tenant_id: str) -> Tenant | None:
        """Get a tenant by ID."""
        result = await self.db.fetch_one(
            """
            SELECT id, name, default_model, rate_limit_per_minute,
                   monthly_token_limit, is_active, metadata, created_at
            FROM tenants
            WHERE id = $1
            """,
            tenant_id,
        )

        if not result:
            return None

        return Tenant(
            id=result["id"],
            name=result["name"],
            api_key="",
            default_model=result["default_model"],
            rate_limit_per_minute=result["rate_limit_per_minute"],
            monthly_token_limit=result["monthly_token_limit"],
            is_active=result["is_active"],
            metadata=result["metadata"] or {},
            created_at=result["created_at"],
        )

    async def list_tenants(self, include_inactive: bool = False) -> list[Tenant]:
        """List all tenants."""
        query = "SELECT * FROM tenants"
        if not include_inactive:
            query += " WHERE is_active = true"
        query += " ORDER BY created_at DESC"

        results = await self.db.fetch_all(query)
        return [
            Tenant(
                id=r["id"],
                name=r["name"],
                api_key="",
                default_model=r["default_model"],
                rate_limit_per_minute=r["rate_limit_per_minute"],
                monthly_token_limit=r["monthly_token_limit"],
                is_active=r["is_active"],
                metadata=r["metadata"] or {},
                created_at=r["created_at"],
            )
            for r in results
        ]

    async def update_tenant(
        self,
        tenant_id: str,
        name: str | None = None,
        default_model: str | None = None,
        rate_limit_per_minute: int | None = None,
        is_active: bool | None = None,
    ) -> bool:
        """Update a tenant's settings."""
        updates = []
        values = []
        idx = 1

        if name is not None:
            updates.append(f"name = ${idx}")
            values.append(name)
            idx += 1
        if default_model is not None:
            updates.append(f"default_model = ${idx}")
            values.append(default_model)
            idx += 1
        if rate_limit_per_minute is not None:
            updates.append(f"rate_limit_per_minute = ${idx}")
            values.append(rate_limit_per_minute)
            idx += 1
        if is_active is not None:
            updates.append(f"is_active = ${idx}")
            values.append(is_active)
            idx += 1

        if not updates:
            return False

        updates.append("updated_at = NOW()")
        values.append(tenant_id)

        await self.db.execute(
            f"UPDATE tenants SET {', '.join(updates)} WHERE id = ${idx}",
            *values,
        )
        return True

    async def grant_agent_access(
        self,
        tenant_id: str,
        agent_id: str,
        model_override: str | None = None,
        is_default: bool = False,
    ) -> None:
        """Grant a tenant access to an agent."""
        await self.db.execute(
            """
            INSERT INTO tenant_agents (tenant_id, agent_id, model_override, is_default)
            VALUES ($1, $2, $3, $4)
            ON CONFLICT (tenant_id, agent_id) DO UPDATE
            SET model_override = $3, is_default = $4
            """,
            tenant_id,
            agent_id,
            model_override,
            is_default,
        )

    async def revoke_agent_access(self, tenant_id: str, agent_id: str) -> None:
        """Revoke a tenant's access to an agent."""
        await self.db.execute(
            "DELETE FROM tenant_agents WHERE tenant_id = $1 AND agent_id = $2",
            tenant_id,
            agent_id,
        )

    async def get_tenant_agents(self, tenant_id: str) -> list[dict[str, Any]]:
        """Get all agents a tenant has access to."""
        return await self.db.fetch_all(
            """
            SELECT a.id, a.name, a.description, ta.model_override, ta.is_default
            FROM agents a
            JOIN tenant_agents ta ON a.id = ta.agent_id
            WHERE ta.tenant_id = $1 AND a.is_active = true
            ORDER BY ta.is_default DESC, a.name
            """,
            tenant_id,
        )

    async def rotate_api_key(self, tenant_id: str) -> str | None:
        """Generate a new API key for a tenant.

        Invalidates the old key immediately.

        Args:
            tenant_id: The tenant's ID.

        Returns:
            The new API key (only returned once), or None if tenant not found.
        """
        # Verify tenant exists
        tenant = await self.get_tenant(tenant_id)
        if not tenant:
            return None

        # Generate new key
        api_key, key_hash = self.generate_api_key()

        # Update tenant with new key
        await self.db.execute(
            """
            UPDATE tenants
            SET api_key = $1, api_key_hash = $2, updated_at = NOW()
            WHERE id = $3
            """,
            api_key[:16] + "...",  # Store truncated for display
            key_hash,
            tenant_id,
        )

        return api_key
