"""
OTP Service
===========
Generates, stores, emails, and validates one-time passwords for
the BizNode workflow engine (e.g. the /verify email-verification flow).

Environment variables used (same as SMTP in email_service.py):
  SMTP_HOST      - SMTP server hostname
  SMTP_PORT      - SMTP server port (default 587)
  SMTP_USER      - SMTP username / sender address
  SMTP_PASSWORD  - SMTP password
  OWNER_EMAIL    - Fallback sender address if SMTP_USER is blank
"""

import os
import hashlib
import secrets
import smtplib
import logging
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime, timedelta

logger = logging.getLogger("biznode.otp")

OTP_EXPIRY_MINUTES = 5
OTP_MAX_ATTEMPTS   = 3


# ── Core OTP helpers ──────────────────────────────────────────────────────────

def generate_otp() -> str:
    """Generate a cryptographically secure 6-digit OTP string."""
    return f"{secrets.randbelow(1_000_000):06d}"


def hash_otp(otp: str) -> str:
    """Return the SHA-256 hex digest of an OTP string."""
    return hashlib.sha256(otp.encode()).hexdigest()


def verify_otp_hash(otp: str, stored_hash: str) -> bool:
    """Constant-time comparison of an OTP against its stored hash."""
    return hashlib.sha256(otp.encode()).hexdigest() == stored_hash


# ── Email delivery ────────────────────────────────────────────────────────────

def send_otp_email(
    to_email: str,
    otp: str,
    subject: str = "",
    context: str = "",
    expiry_text: str = "",
    instructions: str = "",
) -> bool:
    """
    Send the OTP to `to_email` via configured SMTP settings.

    Optional overrides for contextual emails:
      subject      – email subject line (default: "Your BizNode Verification Code")
      context      – explains WHY the user received this OTP (shown before the code)
      expiry_text  – e.g. "24 hours" instead of default "5 minutes"
      instructions – tells the user what to DO with the OTP (shown after the code)

    Returns True on success, False on any failure.
    """
    smtp_host = os.getenv("SMTP_HOST", "")
    smtp_port = int(os.getenv("SMTP_PORT", "587"))
    smtp_user = os.getenv("SMTP_USER", "") or os.getenv("OWNER_EMAIL", "")
    smtp_pass = os.getenv("SMTP_PASSWORD", "")

    if not smtp_host or not smtp_user:
        logger.error("SMTP not configured — cannot send OTP email")
        return False

    if not subject:
        subject = "Your BizNode Verification Code"
    if not expiry_text:
        expiry_text = f"{OTP_EXPIRY_MINUTES} minutes"

    # Build email body
    parts = []
    if context:
        parts.append(context)
        parts.append("")
    parts.append(f"Your verification code is:\n\n  {otp}")
    parts.append(f"\nThis code expires in {expiry_text}.")
    if instructions:
        parts.append("")
        parts.append(instructions)
    parts.append("\nIf you did not request this, please ignore this message.")

    body = "\n".join(parts)

    msg = MIMEMultipart("alternative")
    msg["Subject"] = subject
    msg["From"]    = smtp_user
    msg["To"]      = to_email
    msg.attach(MIMEText(body, "plain"))

    try:
        with smtplib.SMTP(smtp_host, smtp_port, timeout=10) as server:
            server.starttls()
            server.login(smtp_user, smtp_pass)
            server.sendmail(smtp_user, to_email, msg.as_string())
        logger.info("OTP email sent to %s", to_email)
        return True
    except Exception as exc:
        logger.error("Failed to send OTP email: %s", exc)
        return False


# ── High-level OTP lifecycle ──────────────────────────────────────────────────

def create_and_send_otp(user_id: str, email: str, db) -> tuple:
    """
    Generate an OTP, hash it, store in DB, and email it.

    Returns:
        (True,  "OTP sent to <email>")        on success
        (False, "<error description>")         on failure
    """
    otp        = generate_otp()
    otp_hash   = hash_otp(otp)
    expires_at = (datetime.utcnow() + timedelta(minutes=OTP_EXPIRY_MINUTES)).strftime(
        "%Y-%m-%d %H:%M:%S"
    )

    try:
        db.store_otp(str(user_id), email, otp_hash, expires_at)
    except Exception as exc:
        logger.error("Failed to store OTP: %s", exc)
        return False, f"Could not store OTP: {exc}"

    ok = send_otp_email(email, otp)
    if not ok:
        return False, "Failed to send OTP email. Please check SMTP settings."

    return True, f"OTP sent to {email}. It expires in {OTP_EXPIRY_MINUTES} minutes."


def validate_user_otp(user_id: str, otp_input: str, db) -> tuple:
    """
    Validate the OTP entered by the user.

    Handles:
      - No active OTP found (expired or never sent)
      - Max attempts exceeded
      - Correct OTP → marks verified, clears OTP
      - Wrong OTP → increments attempts

    Returns:
        (True,  "OTP verified successfully")   on success
        (False, "<reason>")                    on failure
    """
    otp_input = otp_input.strip()
    row = db.get_active_otp(str(user_id))

    if not row:
        return False, (
            f"No active OTP found. It may have expired (valid for {OTP_EXPIRY_MINUTES} min). "
            "Please restart the workflow to request a new one."
        )

    if row["attempts"] >= OTP_MAX_ATTEMPTS:
        db.clear_otp(str(user_id))
        return False, (
            f"Maximum {OTP_MAX_ATTEMPTS} attempts exceeded. "
            "Workflow cancelled — please start again."
        )

    if verify_otp_hash(otp_input, row["otp_hash"]):
        db.mark_otp_verified(str(user_id))
        db.clear_otp(str(user_id))
        return True, "OTP verified successfully."

    db.increment_otp_attempts(str(user_id))
    remaining = OTP_MAX_ATTEMPTS - (row["attempts"] + 1)
    if remaining <= 0:
        db.clear_otp(str(user_id))
        return False, (
            f"Incorrect OTP. No attempts remaining. "
            "Workflow cancelled — please start again."
        )
    return False, f"Incorrect OTP. {remaining} attempt(s) remaining."
