import logging
import secrets
import string
from datetime import datetime, timedelta, timezone

from sqlalchemy.orm import Session

from app.config import Settings
from app.models import PasswordResetToken, User
from app.models import UserRole
from app.security import hash_password, verify_password
from app.services.email_templates import (
    render_password_reset_otp_html,
    render_password_reset_otp_plain,
    render_verification_otp_html,
    render_verification_otp_plain,
)

logger = logging.getLogger(__name__)


def generate_otp(length: int = 6) -> str:
    alphabet = string.digits
    return "".join(secrets.choice(alphabet) for _ in range(length))


async def _send_email(
    *,
    to_email: str,
    subject: str,
    body: str,
    html_body: str | None = None,
) -> bool:
    settings = Settings()
    if not settings.smtp_host:
        logger.warning("SMTP not configured — email not sent to %s", to_email)
        return False

    import aiosmtplib
    from aiosmtplib.errors import SMTPAuthenticationError, SMTPException
    from email.message import EmailMessage

    recipient = to_email.strip()
    sender = settings.smtp_from.strip()

    msg = EmailMessage()
    msg["Subject"] = subject
    msg["From"] = sender
    msg["To"] = recipient
    msg.set_content(body)
    if html_body:
        msg.add_alternative(html_body, subtype="html")

    logger.info("Sending email from %s to %s (%s)", sender, recipient, subject)

    try:
        await aiosmtplib.send(
            msg,
            hostname=settings.smtp_host,
            port=settings.smtp_port,
            username=settings.smtp_user,
            password=settings.smtp_password.strip(),
            start_tls=settings.smtp_tls,
        )
        logger.info("Email sent to %s", recipient)
        return True
    except SMTPAuthenticationError as exc:
        logger.warning(
            "SMTP login failed for %s@%s:%s — check SMTP_USER / SMTP_PASSWORD in .env: %s",
            settings.smtp_user,
            settings.smtp_host,
            settings.smtp_port,
            exc,
        )
    except (SMTPException, OSError) as exc:
        logger.exception("SMTP send failed for %s: %s", to_email, exc)
    return False


async def send_password_reset_email(to_email: str, otp: str) -> bool:
    return await _send_email(
        to_email=to_email,
        subject="Pole Power Academy — password reset code",
        body=render_password_reset_otp_plain(otp=otp),
        html_body=render_password_reset_otp_html(otp=otp),
    )


async def send_otp_email(to_email: str, otp: str) -> bool:
    """Send a verification / OTP code (registration, etc.)."""
    sent = await _send_email(
        to_email=to_email,
        subject="Pole Power Academy — verification code",
        body=render_verification_otp_plain(otp=otp),
        html_body=render_verification_otp_html(otp=otp),
    )
    if not sent:
        logger.warning("Verification OTP for %s (email not sent): %s", to_email, otp)
    return sent


async def send_member_notification_email(
    to_email: str,
    *,
    title: str,
    message: str,
    notification_type: str,
) -> None:
    settings = Settings()
    if not settings.smtp_host:
        logger.warning(
            "SMTP not configured — notification for %s: %s — %s",
            to_email,
            title,
            message,
        )
        return

    import aiosmtplib
    from aiosmtplib.errors import SMTPAuthenticationError, SMTPException
    from email.message import EmailMessage

    recipient = to_email.strip()
    sender = settings.smtp_from.strip()
    type_label = notification_type.replace("_", " ").title()

    msg = EmailMessage()
    msg["Subject"] = title
    msg["From"] = sender
    msg["To"] = recipient
    msg.set_content(
        f"You have a new {type_label} notification from Pole Power Academy.\n\n"
        f"{title}\n\n"
        f"{message}\n\n"
        f"Log in to the app to view all your notifications."
    )

    logger.info("Sending member notification email from %s to %s", sender, recipient)

    try:
        await aiosmtplib.send(
            msg,
            hostname=settings.smtp_host,
            port=settings.smtp_port,
            username=settings.smtp_user,
            password=settings.smtp_password.strip(),
            start_tls=settings.smtp_tls,
        )
        logger.info("Member notification email sent to %s", recipient)
    except SMTPAuthenticationError as exc:
        logger.warning("SMTP login failed — notification not emailed to %s: %s", recipient, exc)
    except (SMTPException, OSError) as exc:
        logger.exception("SMTP send failed for %s: %s", recipient, exc)


def verify_email_with_otp(db: Session, *, email: str, otp: str) -> User:
    """Validate OTP and mark the user's email as verified (once only)."""
    user = db.query(User).filter(User.email == email, User.role == UserRole.user).first()
    if not user:
        raise ValueError("Invalid request")

    if user.email_verified:
        return user

    token_row = (
        db.query(PasswordResetToken)
        .filter(PasswordResetToken.user_id == user.id, PasswordResetToken.used.is_(False))
        .order_by(PasswordResetToken.created_at.desc())
        .first()
    )
    if not token_row or not verify_password(otp, token_row.otp_hash):
        raise ValueError("Invalid or expired OTP")

    if token_row.expires_at < datetime.now(timezone.utc):
        raise ValueError("OTP expired")

    token_row.used = True
    user.email_verified = True
    db.commit()
    db.refresh(user)
    return user


def create_password_reset_flow(db: Session, user: User) -> str:
    """Invalidate old tokens, create new OTP, return plain OTP for email."""
    db.query(PasswordResetToken).filter(
        PasswordResetToken.user_id == user.id, PasswordResetToken.used.is_(False)
    ).update({"used": True})
    otp = generate_otp()
    row = PasswordResetToken(
        user_id=user.id,
        otp_hash=hash_password(otp),
        expires_at=datetime.now(timezone.utc) + timedelta(minutes=15),
        used=False,
    )
    db.add(row)
    user.otp = otp
    db.commit()
    return otp
