"""
Utility functions for authentication.
Includes email sending functionality and Google OAuth verification.
"""

from django.core.mail import EmailMultiAlternatives
from django.conf import settings
from google.oauth2 import id_token
from google.auth.transport import requests
import jwt
from jwt import PyJWKClient
import logging


from .email_templates import (
    get_otp_email_template,
    get_password_reset_email_template,
    get_welcome_email_template,
    get_business_live_email_template,
    get_new_review_email_template,
    get_weekly_digest_email_template,
    get_team_invite_email_template,
    get_team_access_granted_email_template,
    get_subscription_expiring_email_template,
    get_subscription_expired_email_template,
)

logger = logging.getLogger(__name__)


def _dispatch_email(msg, label, email):
    """
    Send an EmailMultiAlternatives synchronously (EMAIL_TIMEOUT bounds the
    worst case). Previously fired from a daemon thread to keep a slow/
    unreachable SMTP host off the response path — safe under Docker/gunicorn,
    but under cPanel's Passenger the worker process can be recycled right
    after the response is sent, killing the thread before the SMTP round-trip
    (DNS + TCP + TLS + auth) finishes. That silently dropped OTP/welcome/etc.
    emails in production even though SMTP credentials were correct. Every
    request-path email in this module goes through this — the weekly digest
    command (already off the request path) already sent synchronously.
    """
    try:
        msg.send(fail_silently=False)
        logger.info(f"{label} sent successfully to {email}")
    except Exception as e:
        logger.error(f"Failed to send {label} to {email}: {str(e)}")

def send_otp_email(email, otp, first_name):
    """
    Send OTP email for email verification during registration.

    Args:
        email (str): User's email address
        otp (str): Generated OTP
        first_name (str): User's first name
    """
    subject = 'Email Verification - Discovr'

    # Plain text version (fallback)
    text_content = f"""
Hi {first_name},

Thank you for registering with Discovr!

Your One-Time Password (OTP) for email verification is: {otp}

This OTP will expire in {settings.OTP_EXPIRY_MINUTES} minutes.

If you didn't request this, please ignore this email.

Best regards,
Discovr Team
    """

    # HTML version
    html_content = get_otp_email_template(first_name, otp, settings.OTP_EXPIRY_MINUTES)

    msg = EmailMultiAlternatives(
        subject=subject,
        body=text_content,
        from_email=settings.DEFAULT_FROM_EMAIL,
        to=[email]
    )
    msg.attach_alternative(html_content, "text/html")
    _dispatch_email(msg, "OTP email", email)
    return True


def send_password_reset_email(email, temp_password, first_name):
    """
    Send temporary password email for password reset.

    Args:
        email (str): User's email address
        temp_password (str): Generated temporary password
        first_name (str): User's first name
    """
    subject = 'Password Reset - Discovr'

    # Plain text version (fallback)
    text_content = f"""
Hi {first_name},

You requested a password reset for your Discovr account.

Your temporary password is: {temp_password}

Please use this password to log in and change it immediately from your profile settings.

This temporary password will expire in 30 minutes.

If you didn't request this, please contact us immediately.

Best regards,
Discovr Team
    """

    # HTML version
    html_content = get_password_reset_email_template(first_name, temp_password)

    msg = EmailMultiAlternatives(
        subject=subject,
        body=text_content,
        from_email=settings.DEFAULT_FROM_EMAIL,
        to=[email]
    )
    msg.attach_alternative(html_content, "text/html")
    _dispatch_email(msg, "Password reset email", email)
    return True


def send_welcome_email(email, first_name):
    """
    Send welcome email after successful registration.

    Args:
        email (str): User's email address
        first_name (str): User's first name
    """
    subject = 'Welcome to Discovr!'

    # Plain text version (fallback)
    text_content = f"""
Hi {first_name},

Welcome to Discovr!

Your account has been successfully created and verified.

You can now:
- Add your business and showcase your services
- Browse and discover local businesses
- Connect with service providers in your area
- Share reviews and ratings

Start exploring now!

Best regards,
Discovr Team
    """

    # HTML version
    html_content = get_welcome_email_template(first_name)

    msg = EmailMultiAlternatives(
        subject=subject,
        body=text_content,
        from_email=settings.DEFAULT_FROM_EMAIL,
        to=[email]
    )
    msg.attach_alternative(html_content, "text/html")
    _dispatch_email(msg, "Welcome email", email)
    return True


def send_business_live_email(email, first_name, business_title):
    """
    Send email notification when a business is successfully created and goes live.

    Args:
        email (str): User's email address
        first_name (str): User's first name
        business_title (str): Name of the created business
    """
    subject = f'Your Business is Live! - Discovr'

    # Plain text version (fallback)
    text_content = f"""
Hi {first_name},

Congratulations! Your business "{business_title}" is now live on Discovr!

Your business listing is now visible to thousands of potential customers in your area.

What's next?
- Add more photos to showcase your work
- Update your operating hours
- Share your business link with friends and family
- Respond to customer inquiries promptly

Tips for success:
- Keep your contact information up to date
- Encourage satisfied customers to leave reviews
- Update your services and pricing regularly

Thank you for choosing Discovr to grow your business!

Best regards,
Discovr Team
    """

    # HTML version
    html_content = get_business_live_email_template(first_name, business_title)

    msg = EmailMultiAlternatives(
        subject=subject,
        body=text_content,
        from_email=settings.DEFAULT_FROM_EMAIL,
        to=[email]
    )
    msg.attach_alternative(html_content, "text/html")
    _dispatch_email(msg, "Business live email", email)
    return True


def send_new_review_email(email, first_name, business_title, reviewer_name, rating, review_message):
    """
    Send email notification to business owner when someone leaves a review.

    Args:
        email (str): Business owner's email address
        first_name (str): Business owner's first name
        business_title (str): Name of the business
        reviewer_name (str): Name of the person who left the review
        rating (int): Star rating (1-5)
        review_message (str): The review message
    """
    # Create star representation for plain text
    stars = '★' * rating + '☆' * (5 - rating)

    subject = f'New Review on {business_title} - Discovr'

    # Plain text version (fallback)
    text_content = f"""
Hi {first_name},

Great news! Someone just left a review on your business "{business_title}"!

Review Details:
---------------
Reviewer: {reviewer_name}
Rating: {stars} ({rating}/5)

"{review_message}"
---------------

Tips:
- Respond to reviews promptly to show you value customer feedback
- Thank positive reviewers for their support
- Address any concerns professionally

Log in to your account to respond to this review.

Best regards,
Discovr Team
    """

    # HTML version
    html_content = get_new_review_email_template(
        first_name, business_title, reviewer_name, rating, review_message
    )

    msg = EmailMultiAlternatives(
        subject=subject,
        body=text_content,
        from_email=settings.DEFAULT_FROM_EMAIL,
        to=[email]
    )
    msg.attach_alternative(html_content, "text/html")
    _dispatch_email(msg, "New review email", email)
    return True


def send_weekly_digest_email(email, first_name, businesses, unsubscribe_url, personalized=False):
    """
    Send the weekly "picks for you" digest to one newsletter subscriber.

    Args:
        email (str): Subscriber's email address
        first_name (str): Greeting name (falls back to "there" for guests with no account)
        businesses (list[dict]): Each dict has 'title', 'category', 'rating', 'rating_rounded', 'url'
        unsubscribe_url (str): One-click unsubscribe link, unique per subscriber
        personalized (bool): Whether `businesses` came from the subscriber's own lead-event
            history (True) or is a platform-wide featured fallback (False) — changes the intro copy
    """
    subject = 'Your weekly picks from Discovr'

    lines = "\n".join(f"- {b['title']} ({b['category']}, {b['rating']:.1f}/5) — {b['url']}" for b in businesses)
    text_content = f"""
Hi {first_name},

{"Based on the businesses you've recently reached out to, here's what we think you'll like this week:" if personalized else "Here's what's popular on Discovr this week:"}

{lines if businesses else "New businesses are joining Discovr every week — check back soon."}

Explore more: {settings.FRONTEND_URL}/search

---
You're receiving this because you subscribed to the Discovr newsletter.
Unsubscribe: {unsubscribe_url}
    """

    html_content = get_weekly_digest_email_template(first_name, businesses, unsubscribe_url, personalized)

    try:
        msg = EmailMultiAlternatives(
            subject=subject,
            body=text_content,
            from_email=settings.DEFAULT_FROM_EMAIL,
            to=[email]
        )
        msg.attach_alternative(html_content, "text/html")
        msg.send(fail_silently=False)

        logger.info(f"Weekly digest email sent successfully to {email}")
        return True
    except Exception as e:
        logger.error(f"Failed to send weekly digest email to {email}: {str(e)}")
        return False


def send_subscription_expiring_email(email, first_name, business_title, plan_name, days_left):
    """
    Sent from check_expiring_items (the `scheduler` container, off any
    request path) SUBSCRIPTION_WARNING_WINDOW before a paid plan's
    current_period_end — sent synchronously like the weekly digest above,
    for the same reason (nothing here is blocking an HTTP response).
    """
    subject = f'Your {plan_name} plan renews in {days_left} days'

    text_content = f"""
Hi {first_name},

Your {plan_name} plan for "{business_title}" is due to renew in {days_left} days.

Discovr doesn't auto-charge your card — pay now to keep your current features, or do
nothing and your business will automatically move to the Free plan once the period ends.

Renew now: {settings.BUSINESS_APP_URL}/billing

Best regards,
Discovr Team
    """

    html_content = get_subscription_expiring_email_template(
        first_name, business_title, plan_name, days_left, f"{settings.BUSINESS_APP_URL}/billing"
    )

    try:
        msg = EmailMultiAlternatives(subject=subject, body=text_content, from_email=settings.DEFAULT_FROM_EMAIL, to=[email])
        msg.attach_alternative(html_content, "text/html")
        msg.send(fail_silently=False)
        logger.info(f"Subscription-expiring email sent successfully to {email}")
        return True
    except Exception as e:
        logger.error(f"Failed to send subscription-expiring email to {email}: {str(e)}")
        return False


def send_subscription_expired_email(email, first_name, business_title, plan_name):
    """Sent from check_expiring_items once current_period_end has actually passed and the plan was reverted to Free."""
    subject = f'Your {plan_name} plan on {business_title} has ended'

    text_content = f"""
Hi {first_name},

Your {plan_name} plan for "{business_title}" has ended. Since Discovr doesn't
auto-charge your card, your business is now back on the Free plan. Your profile
is still live — some tools (unlimited posts, offers, extra locations and team
seats) are limited again until you upgrade.

Upgrade again: {settings.BUSINESS_APP_URL}/billing

Best regards,
Discovr Team
    """

    html_content = get_subscription_expired_email_template(
        first_name, business_title, plan_name, f"{settings.BUSINESS_APP_URL}/billing"
    )

    try:
        msg = EmailMultiAlternatives(subject=subject, body=text_content, from_email=settings.DEFAULT_FROM_EMAIL, to=[email])
        msg.attach_alternative(html_content, "text/html")
        msg.send(fail_silently=False)
        logger.info(f"Subscription-expired email sent successfully to {email}")
        return True
    except Exception as e:
        logger.error(f"Failed to send subscription-expired email to {email}: {str(e)}")
        return False


def send_team_invite_email(email, first_name, business_title, role, temp_password):
    """
    Sent when a business owner adds someone to their team who doesn't have
    a Discovr account yet — a new account is created for them with a
    system-generated temporary password, emailed here (same pattern as
    send_password_reset_email, but this password isn't time-limited since
    it's not tied to a reset token — it's just their new account password).
    """
    subject = f"You've been added to {business_title} on Discovr"

    text_content = f"""
Hi {first_name},

You've been given {role} access to "{business_title}" on Discovr. An account has been created for you.

Email: {email}
Temporary password: {temp_password}

Log in to the Discovr business dashboard with these credentials, then change your password from Settings.

Best regards,
Discovr Team
    """

    html_content = get_team_invite_email_template(first_name, business_title, role, email, temp_password)

    msg = EmailMultiAlternatives(
        subject=subject,
        body=text_content,
        from_email=settings.DEFAULT_FROM_EMAIL,
        to=[email]
    )
    msg.attach_alternative(html_content, "text/html")
    _dispatch_email(msg, "Team invite email", email)
    return True


def send_team_access_granted_email(email, first_name, business_title, role):
    """
    Sent when a business owner adds someone who already has a Discovr
    account — no new credentials needed, just a notification that they
    now have access to another business.
    """
    subject = f"You've been added to {business_title} on Discovr"

    text_content = f"""
Hi {first_name},

You've been given {role} access to "{business_title}" on Discovr. Log in to your existing account and you'll see it in your dashboard.

Best regards,
Discovr Team
    """

    html_content = get_team_access_granted_email_template(first_name, business_title, role)

    msg = EmailMultiAlternatives(
        subject=subject,
        body=text_content,
        from_email=settings.DEFAULT_FROM_EMAIL,
        to=[email]
    )
    msg.attach_alternative(html_content, "text/html")
    _dispatch_email(msg, "Team access granted email", email)
    return True


def _extract_user_info_from_payload(payload):
    """Convert a Google token payload dict into the standard user-info dict."""
    email_verified = payload.get('email_verified')
    # tokeninfo endpoint returns "true"/"false" strings; google-auth returns bool
    if isinstance(email_verified, str):
        email_verified = email_verified.lower() == 'true'
    return {
        'email': payload.get('email'),
        'first_name': payload.get('given_name', ''),
        'last_name': payload.get('family_name', ''),
        'google_id': payload.get('sub'),
        'profile_picture': payload.get('picture', ''),
        'email_verified': bool(email_verified),
    }


def _check_audience(token_aud, expected):
    """Return True if expected client-id is an acceptable audience in the token."""
    if isinstance(token_aud, list):
        return expected in token_aud
    return token_aud == expected


def verify_google_token(id_token_string):
    """
    Verify a Google ID token and return user information.

    Strategy:
      1. Call Google's tokeninfo endpoint — no local key-fetching, immune to
         clock-skew, works reliably inside Docker.
      2. Fall back to the google-auth library in case the endpoint is unreachable.

    Returns:
        dict with user info, or None on failure.
    """
    import json
    import urllib.request
    import urllib.error

    expected_client_id = (settings.GOOGLE_CLIENT_ID or '').strip()
    logger.info(f"Verifying Google token — expected audience: {expected_client_id}")

    # ── Primary: Google tokeninfo REST endpoint ──────────────────────────────
    try:
        url = f"https://oauth2.googleapis.com/tokeninfo?id_token={id_token_string}"
        req = urllib.request.Request(url, headers={'Accept': 'application/json'})
        with urllib.request.urlopen(req, timeout=10) as response:
            payload = json.loads(response.read().decode('utf-8'))

        token_aud = payload.get('aud')
        if expected_client_id and not _check_audience(token_aud, expected_client_id):
            logger.error(
                f"tokeninfo aud mismatch — token aud: {token_aud!r}, "
                f"expected: {expected_client_id!r}"
            )
            return None

        logger.info(f"Google token verified via tokeninfo for: {payload.get('email')}")
        return _extract_user_info_from_payload(payload)

    except urllib.error.HTTPError as e:
        # tokeninfo returns 400 for invalid/expired tokens
        body = e.read().decode('utf-8', errors='replace')
        logger.error(f"tokeninfo HTTP {e.code}: {body}")
    except Exception as e:
        logger.warning(f"tokeninfo endpoint unavailable ({type(e).__name__}): {e} — trying google-auth fallback")

    # ── Fallback: google-auth library ────────────────────────────────────────
    try:
        idinfo = id_token.verify_oauth2_token(
            id_token_string,
            requests.Request(),
            expected_client_id if expected_client_id else None,
        )

        # Manual audience check (handles list-typed aud claims)
        if expected_client_id:
            token_aud = idinfo.get('aud')
            if not _check_audience(token_aud, expected_client_id):
                logger.error(
                    f"google-auth aud mismatch — token aud: {token_aud!r}, "
                    f"expected: {expected_client_id!r}"
                )
                return None

        logger.info(f"Google token verified via google-auth for: {idinfo.get('email')}")
        return _extract_user_info_from_payload(idinfo)

    except ValueError as e:
        logger.error(f"google-auth verification failed (ValueError): {e}")
    except Exception as e:
        logger.error(f"google-auth verification failed ({type(e).__name__}): {e}")

    return None


# Cached client for Apple's public signing keys (JWKS). PyJWKClient fetches
# https://appleid.apple.com/auth/keys lazily and caches keys in-memory, so a
# single module-level instance avoids refetching on every login request.
_apple_jwk_client = None


def _get_apple_jwk_client():
    global _apple_jwk_client
    if _apple_jwk_client is None:
        _apple_jwk_client = PyJWKClient(
            "https://appleid.apple.com/auth/keys", cache_keys=True
        )
    return _apple_jwk_client


def verify_apple_token(identity_token):
    """
    Verify a Sign in with Apple identity token (JWS) and return user info.

    The token is signed by Apple with RS256; its audience (aud) must match
    this app's bundle identifier since the token is minted by the native
    AuthenticationServices flow on iOS, not a web "Services ID" flow.

    Returns:
        dict with user info, or None on failure.
    """
    expected_client_id = (settings.APPLE_CLIENT_ID or '').strip()

    try:
        signing_key = _get_apple_jwk_client().get_signing_key_from_jwt(identity_token)
        payload = jwt.decode(
            identity_token,
            signing_key.key,
            algorithms=['RS256'],
            audience=expected_client_id if expected_client_id else None,
            issuer='https://appleid.apple.com',
            options={'verify_aud': bool(expected_client_id)},
        )

        email_verified = payload.get('email_verified')
        if isinstance(email_verified, str):
            email_verified = email_verified.lower() == 'true'

        is_private_email = payload.get('is_private_email')
        if isinstance(is_private_email, str):
            is_private_email = is_private_email.lower() == 'true'

        logger.info(f"Apple token verified for: {payload.get('email')}")
        return {
            'email': payload.get('email'),
            'apple_id': payload.get('sub'),
            'email_verified': bool(email_verified),
            'is_private_email': bool(is_private_email),
        }
    except jwt.PyJWTError as e:
        logger.error(f"Apple token verification failed: {e}")
    except Exception as e:
        logger.error(f"Apple token verification error ({type(e).__name__}): {e}")

    return None
