import hashlib
import logging
import random
from datetime import datetime, timedelta, timezone

from sqlalchemy.orm import Session

from app.core.config import get_settings
from app.core.exceptions import ValidationError
from app.core.security import get_password_hash
from app.models.password_reset_otp import PasswordResetOtp
from app.repositories.user_repository import UserRepository
from app.utils.otp_delivery import send_otp_email, send_otp_sms

logger = logging.getLogger(__name__)


class PasswordResetService:
    OTP_EXPIRE_MINUTES = 10

    def __init__(self, db: Session):
        self.db = db
        self.users = UserRepository(db)
        self.settings = get_settings()

    def request_reset(self, email: str, channel: str) -> dict:
        user = self.users.get_by_email(email)
        if not user or not user.is_active:
            return {
                "message": "If an account exists, a reset code has been sent.",
                "channel": channel,
            }

        otp = f"{random.randint(0, 999999):06d}"
        otp_hash = self._hash_otp(otp)
        expires_at = datetime.now(timezone.utc) + timedelta(minutes=self.OTP_EXPIRE_MINUTES)

        self.db.query(PasswordResetOtp).filter(
            PasswordResetOtp.user_id == user.id,
            PasswordResetOtp.used_at.is_(None),
        ).update({"used_at": datetime.now(timezone.utc)}, synchronize_session=False)

        self.db.add(
            PasswordResetOtp(
                user_id=user.id,
                otp_hash=otp_hash,
                channel=channel,
                expires_at=expires_at,
                created_at=datetime.now(timezone.utc),
            )
        )
        self.db.commit()

        if channel == "sms":
            if not user.mobile:
                raise ValidationError("SMS reset is not available — no mobile number on your account.")
            send_otp_sms(user.mobile, otp)
        else:
            send_otp_email(user.email, otp)

        response = {
            "message": "If an account exists, a reset code has been sent.",
            "channel": channel,
        }
        if self.settings.debug:
            response["debug_otp"] = otp
            logger.info("Password reset OTP for %s: %s", email, otp)
        return response

    def reset_password(self, email: str, otp: str, new_password: str) -> None:
        user = self.users.get_by_email(email)
        if not user or not user.is_active:
            raise ValidationError("Invalid or expired reset code")

        record = (
            self.db.query(PasswordResetOtp)
            .filter(
                PasswordResetOtp.user_id == user.id,
                PasswordResetOtp.used_at.is_(None),
                PasswordResetOtp.expires_at > datetime.now(timezone.utc),
            )
            .order_by(PasswordResetOtp.created_at.desc())
            .first()
        )
        if not record or record.otp_hash != self._hash_otp(otp):
            raise ValidationError("Invalid or expired reset code")

        user.hashed_password = get_password_hash(new_password)
        record.used_at = datetime.now(timezone.utc)
        user.failed_login_attempts = 0
        user.locked_until = None
        self.db.commit()

    def _hash_otp(self, otp: str) -> str:
        payload = f"{self.settings.secret_key}:{otp}".encode()
        return hashlib.sha256(payload).hexdigest()
