import logging
from typing import Tuple
from sqlalchemy.orm import Session
from sqlalchemy import select
from src.core.providers.base import BasePaymentProvider, ProviderConfig
from src.core.providers.registry import ProviderRegistry
from src.core.config import settings
from src.core.exceptions import ForbiddenError, NotFoundError
from src.apps.payment_providers.helpers.credentials import decrypt_credential

logger = logging.getLogger("providers.factory")


async def get_provider_for_merchant(
    merchant,
    db: Session,
) -> Tuple[BasePaymentProvider, ProviderConfig]:
    """
    Resolve the active payment provider for a merchant.

    If merchant.active_provider_id is None (legacy path), returns PayrixProvider
    with credentials sourced from environment variables.
    """
    if merchant.active_provider_id is None:
        # Legacy fallback: build TSYS credentials from environment variables.
        # This path is used only when a merchant has not completed provider
        # onboarding (active_provider_id is NULL).  Run the seed script
        # (scripts/seed_tsys_credentials.py) to migrate to DB-backed credentials.
        from src.core.providers.implementations.tsys import TSYSProvider
        provider = TSYSProvider()
        config = ProviderConfig(
            provider_slug="tsys",
            credentials={
                "merchant_id":    settings.TSYS_MERCHANT_ID or "",
                "device_id":      settings.TSYS_DEVICE_ID or "",
                "user_id":        settings.TSYS_USER_ID or "",
                "password":       settings.TSYS_PASSWORD or "",
                "developer_id":   settings.TSYS_DEVELOPER_ID or "",
                "api_base_url":   settings.TSYS_API_BASE_URL or "",
                "tsep_base_url":  settings.TSYS_TSEP_BASE_URL or "",
                "tsys_env":       settings.TSYS_ENV or "test",
                "webhook_secret": settings.TSYS_WEBHOOK_SECRET or "",
            },
            config_data={},
        )
        return provider, config

    # Load merchant provider config
    from src.apps.payment_providers.models.merchant_provider_config import MerchantProviderConfig
    from src.apps.payment_providers.models.payment_provider import PaymentProvider
    from src.apps.payment_providers.models.merchant_provider_credential import MerchantProviderCredential

    stmt = select(MerchantProviderConfig).where(
        MerchantProviderConfig.merchant_id == merchant.id,
        MerchantProviderConfig.provider_id == merchant.active_provider_id,
        MerchantProviderConfig.is_active == True,
        MerchantProviderConfig.deleted_at.is_(None),
    )
    mpc = db.execute(stmt).scalar_one_or_none()
    if not mpc:
        raise ForbiddenError(message="Merchant has no active provider configuration. Please complete onboarding.")

    if mpc.onboarding_status != "active":
        raise ForbiddenError(
            message=f"Provider onboarding is not complete (status: {mpc.onboarding_status}). Please finish onboarding before processing payments."
        )

    # Load provider record first so the slug is validated before touching credentials.
    provider_stmt = select(PaymentProvider).where(
        PaymentProvider.id == merchant.active_provider_id,
        PaymentProvider.is_active == True,
        PaymentProvider.deleted_at.is_(None),
    )
    payment_provider = db.execute(provider_stmt).scalar_one_or_none()
    if not payment_provider:
        raise NotFoundError(message="Payment provider not found or is inactive.")

    # Load and decrypt credentials using the shared helper (which handles
    # InvalidToken gracefully without leaking ciphertext in error messages).
    creds_stmt = select(MerchantProviderCredential).where(
        MerchantProviderCredential.merchant_provider_config_id == mpc.id
    )
    raw_creds = db.execute(creds_stmt).scalars().all()
    credentials = {}
    for cred in raw_creds:
        # decrypt_credential raises InternalServerError on InvalidToken —
        # it never logs ciphertext or exposes the encrypted value to callers.
        credentials[cred.credential_key] = decrypt_credential(cred.credential_value)

    provider = ProviderRegistry.get(payment_provider.slug)
    config = ProviderConfig(
        provider_slug=payment_provider.slug,
        credentials=credentials,
        config_data=mpc.config_data or {},
    )
    return provider, config
