# app/modules/loyalty/services/google_wallet_service.py
"""
Google Wallet service.

Handles Google Wallet integration including:
- Creating LoyaltyClass for programs
- Creating LoyaltyObject for cards
- Updating objects on balance changes
- Generating "Add to Wallet" URLs
"""

import json
import logging
from typing import Any

from sqlalchemy.orm import Session

from app.modules.loyalty.config import config
from app.modules.loyalty.exceptions import (
    GoogleWalletNotConfiguredException,
    WalletIntegrationException,
)
from app.modules.loyalty.models import LoyaltyCard, LoyaltyProgram

logger = logging.getLogger(__name__)


class GoogleWalletService:
    """Service for Google Wallet integration."""

    def __init__(self):
        """Initialize the Google Wallet service."""
        self._credentials = None
        self._http_client = None

    @property
    def is_configured(self) -> bool:
        """Check if Google Wallet is configured."""
        return bool(config.google_issuer_id and config.google_service_account_json)

    def _get_credentials(self):
        """Get Google service account credentials."""
        if self._credentials:
            return self._credentials

        if not config.google_service_account_json:
            raise GoogleWalletNotConfiguredException()

        try:
            from google.oauth2 import service_account

            scopes = ["https://www.googleapis.com/auth/wallet_object.issuer"]

            self._credentials = service_account.Credentials.from_service_account_file(
                config.google_service_account_json,
                scopes=scopes,
            )
            return self._credentials
        except Exception as e:
            logger.error(f"Failed to load Google credentials: {e}")
            raise WalletIntegrationException("google", str(e))

    def _get_http_client(self):
        """Get authenticated HTTP client."""
        if self._http_client:
            return self._http_client

        try:
            from google.auth.transport.requests import AuthorizedSession

            credentials = self._get_credentials()
            self._http_client = AuthorizedSession(credentials)
            return self._http_client
        except Exception as e:
            logger.error(f"Failed to create Google HTTP client: {e}")
            raise WalletIntegrationException("google", str(e))

    # =========================================================================
    # LoyaltyClass Operations (Program-level)
    # =========================================================================

    def create_class(self, db: Session, program: LoyaltyProgram) -> str:
        """
        Create a LoyaltyClass for a loyalty program.

        Args:
            db: Database session
            program: Loyalty program

        Returns:
            Google Wallet class ID
        """
        if not self.is_configured:
            raise GoogleWalletNotConfiguredException()

        issuer_id = config.google_issuer_id
        class_id = f"{issuer_id}.loyalty_program_{program.id}"

        class_data = {
            "id": class_id,
            "issuerId": issuer_id,
            "reviewStatus": "UNDER_REVIEW",
            "programName": program.display_name,
            "programLogo": {
                "sourceUri": {
                    "uri": program.logo_url or "https://via.placeholder.com/100",
                },
            },
            "hexBackgroundColor": program.card_color,
            "localizedProgramName": {
                "defaultValue": {
                    "language": "en",
                    "value": program.display_name,
                },
            },
        }

        # Add hero image if configured
        if program.hero_image_url:
            class_data["heroImage"] = {
                "sourceUri": {"uri": program.hero_image_url},
            }

        try:
            http = self._get_http_client()
            response = http.post(
                "https://walletobjects.googleapis.com/walletobjects/v1/loyaltyClass",
                json=class_data,
            )

            if response.status_code in (200, 201):
                # Update program with class ID
                program.google_class_id = class_id
                db.commit()

                logger.info(f"Created Google Wallet class {class_id} for program {program.id}")
                return class_id
            elif response.status_code == 409:
                # Class already exists
                program.google_class_id = class_id
                db.commit()
                return class_id
            else:
                error = response.json() if response.text else {}
                raise WalletIntegrationException(
                    "google",
                    f"Failed to create class: {response.status_code} - {error}",
                )
        except WalletIntegrationException:
            raise
        except Exception as e:
            logger.error(f"Failed to create Google Wallet class: {e}")
            raise WalletIntegrationException("google", str(e))

    def update_class(self, db: Session, program: LoyaltyProgram) -> None:
        """Update a LoyaltyClass when program settings change."""
        if not program.google_class_id:
            return

        class_data = {
            "programName": program.display_name,
            "hexBackgroundColor": program.card_color,
        }

        if program.logo_url:
            class_data["programLogo"] = {
                "sourceUri": {"uri": program.logo_url},
            }

        try:
            http = self._get_http_client()
            response = http.patch(
                f"https://walletobjects.googleapis.com/walletobjects/v1/loyaltyClass/{program.google_class_id}",
                json=class_data,
            )

            if response.status_code not in (200, 201):
                logger.warning(
                    f"Failed to update Google Wallet class {program.google_class_id}: "
                    f"{response.status_code}"
                )
        except Exception as e:
            logger.error(f"Failed to update Google Wallet class: {e}")

    # =========================================================================
    # LoyaltyObject Operations (Card-level)
    # =========================================================================

    def create_object(self, db: Session, card: LoyaltyCard) -> str:
        """
        Create a LoyaltyObject for a loyalty card.

        Args:
            db: Database session
            card: Loyalty card

        Returns:
            Google Wallet object ID
        """
        if not self.is_configured:
            raise GoogleWalletNotConfiguredException()

        program = card.program
        if not program.google_class_id:
            # Create class first
            self.create_class(db, program)

        issuer_id = config.google_issuer_id
        object_id = f"{issuer_id}.loyalty_card_{card.id}"

        object_data = self._build_object_data(card, object_id)

        try:
            http = self._get_http_client()
            response = http.post(
                "https://walletobjects.googleapis.com/walletobjects/v1/loyaltyObject",
                json=object_data,
            )

            if response.status_code in (200, 201):
                card.google_object_id = object_id
                db.commit()

                logger.info(f"Created Google Wallet object {object_id} for card {card.id}")
                return object_id
            elif response.status_code == 409:
                # Object already exists
                card.google_object_id = object_id
                db.commit()
                return object_id
            else:
                error = response.json() if response.text else {}
                raise WalletIntegrationException(
                    "google",
                    f"Failed to create object: {response.status_code} - {error}",
                )
        except WalletIntegrationException:
            raise
        except Exception as e:
            logger.error(f"Failed to create Google Wallet object: {e}")
            raise WalletIntegrationException("google", str(e))

    def update_object(self, db: Session, card: LoyaltyCard) -> None:
        """Update a LoyaltyObject when card balance changes."""
        if not card.google_object_id:
            return

        object_data = self._build_object_data(card, card.google_object_id)

        try:
            http = self._get_http_client()
            response = http.patch(
                f"https://walletobjects.googleapis.com/walletobjects/v1/loyaltyObject/{card.google_object_id}",
                json=object_data,
            )

            if response.status_code in (200, 201):
                logger.debug(f"Updated Google Wallet object for card {card.id}")
            else:
                logger.warning(
                    f"Failed to update Google Wallet object {card.google_object_id}: "
                    f"{response.status_code}"
                )
        except Exception as e:
            logger.error(f"Failed to update Google Wallet object: {e}")

    def _build_object_data(self, card: LoyaltyCard, object_id: str) -> dict[str, Any]:
        """Build the LoyaltyObject data structure."""
        program = card.program

        object_data = {
            "id": object_id,
            "classId": program.google_class_id,
            "state": "ACTIVE" if card.is_active else "INACTIVE",
            "accountId": card.card_number,
            "accountName": card.card_number,
            "barcode": {
                "type": "QR_CODE",
                "value": card.qr_code_data,
            },
        }

        # Add loyalty points (stamps as points for display)
        if program.is_stamps_enabled:
            object_data["loyaltyPoints"] = {
                "label": "Stamps",
                "balance": {
                    "int": card.stamp_count,
                },
            }
            # Add secondary points showing target
            object_data["secondaryLoyaltyPoints"] = {
                "label": f"of {program.stamps_target}",
                "balance": {
                    "int": program.stamps_target,
                },
            }
        elif program.is_points_enabled:
            object_data["loyaltyPoints"] = {
                "label": "Points",
                "balance": {
                    "int": card.points_balance,
                },
            }

        return object_data

    # =========================================================================
    # Save URL Generation
    # =========================================================================

    def get_save_url(self, db: Session, card: LoyaltyCard) -> str:
        """
        Get the "Add to Google Wallet" URL for a card.

        Args:
            db: Database session
            card: Loyalty card

        Returns:
            URL for adding pass to Google Wallet
        """
        if not self.is_configured:
            raise GoogleWalletNotConfiguredException()

        # Ensure object exists
        if not card.google_object_id:
            self.create_object(db, card)

        # Generate JWT for save link
        try:
            import jwt
            from datetime import datetime, timedelta

            credentials = self._get_credentials()

            claims = {
                "iss": credentials.service_account_email,
                "aud": "google",
                "origins": [],
                "typ": "savetowallet",
                "payload": {
                    "loyaltyObjects": [{"id": card.google_object_id}],
                },
                "iat": datetime.utcnow(),
                "exp": datetime.utcnow() + timedelta(hours=1),
            }

            # Sign with service account private key
            token = jwt.encode(
                claims,
                credentials._signer._key,
                algorithm="RS256",
            )

            card.google_object_jwt = token
            db.commit()

            return f"https://pay.google.com/gp/v/save/{token}"
        except Exception as e:
            logger.error(f"Failed to generate Google Wallet save URL: {e}")
            raise WalletIntegrationException("google", str(e))


# Singleton instance
google_wallet_service = GoogleWalletService()
