# app/services/user_service.py
from datetime import datetime
from typing import Optional, List, Dict
from sqlalchemy.orm import Session
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import text
from app.db.session import get_db
from app.models.user import User
from app.schemas import user as user_schemas
from app.core.security import pwd_context, create_access_token, get_password_hash, create_refresh_token, verify_refresh_token, generate_verification_code
import uuid
import logging
import random
import string
from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks
from pydantic import BaseModel, EmailStr
from app.services.email_service import send_verification_email
from app.models.password_reset import PasswordReset
from datetime import timedelta
from app.core.time import now
from app.models.user_referral import UserReferral
from app.models.point_transaction import PointTransaction
from app.models.user_points import UserPoints
from app.core.config import settings
from app.services.notification_service import NotificationService
from app.services.friendship_service import create_friendship
from app.services.admin_service import get_setting_float, get_setting_int

logger = logging.getLogger('adimsayar')

class UserService:
    @staticmethod
    def get_user(db: Session, user_id: int) -> Optional[User]:
        try:
            logger.info(f"Fetching user with id: {user_id}")
            user = db.query(User).filter(User.id == user_id).first()
            if user:
                logger.info(f"Found user with id: {user_id}")
            else:
                logger.warning(f"No user found with id: {user_id}")
            return user
        except SQLAlchemyError as e:
            logger.error(f"Database error while fetching user {user_id}: {str(e)}")
            raise

    @staticmethod
    def get_user_by_email(db: Session, email: str) -> Optional[User]:
        try:
            logger.info(f"Fetching user with email: {email}")
            user = db.query(User).filter(User.email == email, User.status == 1).first()
            if user:
                logger.info(f"Found user with email: {email}")
            else:
                logger.warning(f"No active user found with email: {email}")
            return user
        except SQLAlchemyError as e:
            logger.error(f"Database error while fetching user by email {email}: {str(e)}")
            raise

    @staticmethod
    def get_users(
        db: Session, skip: int = 0, limit: int = 100
    ) -> List[Dict]:
        try:
            # Check database connection
            db_check = db.execute(text("SELECT DATABASE()")).scalar()
            logger.info(f"Connected to database: {db_check}")
            
            # Count total records in users table
            count_query = text("SELECT COUNT(*) FROM users")
            total_count = db.execute(count_query).scalar()
            logger.info(f"Total records in users table: {total_count}")
            
            # Use SELECT * to fetch all columns
            query = text("""
                SELECT *
                FROM users
                WHERE status = 1
                ORDER BY id ASC
                LIMIT :skip, :limit
            """)
            
            result = db.execute(query, {"skip": skip, "limit": limit})
            users = []
            
            for row in result:
                user_dict = dict(row._mapping)
                
                # If the key 'birth_date' is missing but 'birthday' exists, map it accordingly.
                if "birth_date" not in user_dict and "birthday" in user_dict:
                    user_dict["birth_date"] = user_dict["birthday"]
                
                logger.debug(f"Found user: ID={user_dict.get('id')}, Email={user_dict.get('email')}")
                
                # Convert datetime objects to ISO format strings (if any exist)
                for field in ['created_at', 'updated_at', 'last_login']:
                    if field in user_dict and user_dict[field]:
                        user_dict[field] = user_dict[field].isoformat()
                
                users.append(user_dict)
            
            logger.info(f"Total users found: {len(users)}")
            return users
            
        except Exception as e:
            logger.error(f"Error in get_users: {str(e)}", exc_info=True)
            raise

    @staticmethod
    def generate_reference_code() -> str:
        """Generate a unique 8-character reference code"""
        chars = string.ascii_uppercase + string.digits
        return ''.join(random.choices(chars, k=6))

    @staticmethod
    def create_user(db: Session, user: user_schemas.UserCreate) -> User:
        try:
            # 고유 레퍼런스 코드를 생성합니다.
            while True:
                reference_code = UserService.generate_reference_code()
                exists = db.query(User).filter(User.reference_code == reference_code).first()
                if not exists:
                    break

            db_user = User(
                email=user.email,
                password=get_password_hash(user.password),
                first_name=user.first_name,
                last_name=user.last_name,
                phone_code=user.phone_code,
                phone=user.phone,
                gender=user.gender,
                birth_date=user.birth_date,
                profile_image=user.profile_image,
                reference_code=reference_code,
                reference_code_used=user.reference_code_used,
                daily_step_goal=10000,
                country_code=user.country_code
            )
            
            db.add(db_user)
            db.commit()
            db.refresh(db_user)
            
            # 레퍼럴 코드를 입력한 경우
            if db_user.reference_code_used:
                referrer = db.query(User).filter(User.reference_code == db_user.reference_code_used).first()
                if referrer:
                    # 1. 추천 이벤트 기록 (보너스 지급 등 후속 처리를 위한 user_referral 테이블에 저장)
                    referral = UserReferral(
                        referrer_user_id=db_user.id,  # 새로운 사용자가 referrer_user_id
                        referred_user_id=referrer.id,  # 이미 존재하는 사용자가 referred_user_id
                        awarded=True  # 회원가입 시 즉시 보상 지급 표시
                    )
                    db.add(referral)
                    
                    # 2. 친구 관계 생성
                    from app.services import friendship_service
                    friendship_service.create_friendship(
                        db, 
                        user_id=referrer.id,   # 추천인 입장에서 친구 요청
                        friend_id=db_user.id,  # 신규 가입자
                        status="accepted"       # 자동 수락 상태로 생성
                    )
                    
                    # 3. 회원가입 시 바로 포인트 지급
                    country_code = db_user.country_code
                    bonus_points = get_setting_int(db, "REFERRAL_BONUS", country_code)
                    if bonus_points <= 0:
                        # 설정값이 없거나 0 이하인 경우 기본값 사용
                        bonus_points = 100
                    
                    # 추천인에게 포인트 지급
                    point_tx = PointTransaction(
                        user_id=referrer.id,
                        transaction_type="referral_bonus",
                        amount=bonus_points,
                        description="Referral bonus for new user registration"
                    )
                    db.add(point_tx)

                    user_points_record = db.query(UserPoints).filter(UserPoints.user_id == referrer.id).first()
                    if user_points_record:
                        user_points_record.total_points += bonus_points
                    else:
                        user_points_record = UserPoints(user_id=referrer.id, total_points=bonus_points)
                        db.add(user_points_record)

                    # 신규 가입자에게도 포인트 지급
                    point_tx_referred = PointTransaction(
                        user_id=db_user.id,
                        transaction_type="referral_bonus",  # 통일성을 위해 transaction_type 변경
                        amount=bonus_points,
                        description="Signup bonus for using referral code"
                    )
                    db.add(point_tx_referred)

                    referred_points_record = db.query(UserPoints).filter(UserPoints.user_id == db_user.id).first()
                    if referred_points_record:
                        referred_points_record.total_points += bonus_points
                    else:
                        referred_points_record = UserPoints(user_id=db_user.id, total_points=bonus_points)
                        db.add(referred_points_record)

                    # 추천인에게 알림 전송
                    NotificationService.create_notification(
                        db, 
                        user_id=referrer.id, 
                        notif_type="award_referral_point",
                        title="Tebrikler! Referans Bonusu Kazandınız!",
                        message=f"Arkadaşınızın kaydı sayesinde {bonus_points} puan bonus kazandınız."
                    )
                    
                    # 변경사항 커밋
                    db.commit()
            
            return db_user
            
        except Exception as e:
            logger.error(f"Error creating user: {str(e)}")
            db.rollback()
            raise

    @staticmethod
    def update_user(db: Session, user_id: int, user: user_schemas.UserUpdate) -> Optional[User]:
        try:
            db_user = db.query(User).filter(User.id == user_id).first()
            if not db_user:
                return None

            if user.full_name:
                first_name, last_name = user.full_name.split()
                db_user.first_name = first_name
                db_user.last_name = last_name

            if user.email:
                db_user.email = user.email
            if user.phone:
                db_user.phone = user.phone
            if user.daily_step_goal:
                db_user.daily_step_goal = user.daily_step_goal
            if user.profile_image:
                db_user.profile_image = user.profile_image

            # Do not update gender or birth_date during profile update.
            db_user.updated_at = datetime.utcnow()
            db.commit()
            db.refresh(db_user)
            return db_user

        except Exception as e:
            logger.error(f"Error updating user: {str(e)}")
            db.rollback()
            raise

    @staticmethod
    def delete_user(db: Session, user_id: int) -> Optional[User]:
        try:
            logger.info(f"Deleting user with id: {user_id}")
            db_user = UserService.get_user(db, user_id)
            if not db_user:
                logger.warning(f"No active user found with id: {user_id} for deletion")
                return None
                
            db_user.status = 0 # 0: deleted, 1: active
            db_user.updated_at = datetime.utcnow()
            db.commit()
            logger.info(f"Successfully deleted user with id: {user_id}")
            return db_user
        except SQLAlchemyError as e:
            logger.error(f"Database error while deleting user {user_id}: {str(e)}")
            db.rollback()
            raise
    
    @staticmethod
    def reset_password(db: Session, email: str, new_password: str):
        """
        Reset the password for the user with the given email.
        """
        user = UserService.get_user_by_email(db, email)
        if not user:
            from fastapi import HTTPException, status  # 여기서 가져오거나 상단에 이미 가져오세요.
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="User not found"
            )
        
        hashed_password = get_password_hash(new_password)
        user.password = hashed_password
        db.commit()
        db.refresh(user)
        logger.info(f"Password updated successfully for user: {email}")
        return user

    @staticmethod
    def authenticate_user(
        db: Session, email: str, password: str, background_tasks: Optional[BackgroundTasks] = None
    ) -> Optional[Dict]:
        try:
            logger.info(f"Attempting authentication for user: {email}")
            user = db.query(User).filter(User.email == email).first()
            
            if not user:
                logger.warning(f"User not found with email: {email}")
                return None
                
            if not pwd_context.verify(password, user.password):
                logger.warning(f"Invalid password for user: {email}")
                return None
                
            if user.status == 0:
                logger.warning(f"Inactive user attempted to login: {email}")
                return None
            
            # 새 사용자인지 확인: last_login 값이 None이면 최초 로그인을 의미
            is_new_user = user.last_login is None

            # 마지막 로그인 시간 업데이트
            user.last_login = now()
            db.add(user)
            db.commit()
            db.refresh(user)

            # Create access and refresh tokens
            access_token = create_access_token(subject=user.id)
            refresh_token = create_refresh_token(subject=user.id)

            # 로그인 응답 데이터 구성
            response = {
                "access_token": access_token,
                "refresh_token": refresh_token,
                "token_type": "bearer",
                "user": {
                    "id": user.id,
                    "email": user.email,
                    "first_name": user.first_name,
                    "last_name": user.last_name,
                    "status": user.status
                }
            }

            # 새 사용자일 경우 환영 메시지 전달 (백그라운드 태스크에서 처리)
            welcome_message = "Tebrikler, adimsayar ile birlikte sağlıklı bir yaşamın tadını çıkarın!"
            if is_new_user:
                response["welcome_message"] = welcome_message
                logger.debug("New Client Login – Welcome Message Sent")
                
                # 백그라운드 태스크가 제공된 경우 비동기로 알림 전송
                if background_tasks is not None:
                    background_tasks.add_task(
                        UserService._send_welcome_notification,
                        db=db,
                        user_id=user.id,
                        title="Hoş Geldiniz!",
                        message=welcome_message
                    )
                else:
                    # 백그라운드 태스크가 없으면 동기적으로 알림만 생성 (푸시는 나중에)
                    notification = NotificationService.create_notification(
                        db=db,
                        user_id=user.id,
                        notif_type="hoş_geliş",
                        title="Hoş Geldiniz!",
                        message=welcome_message
                    )
                    db.add(notification)
                    db.commit()
                
                logger.debug("Welcome notification created")

            return response
            
        except Exception as e:
            logger.error(f"Authentication error: {str(e)}", exc_info=True)
            raise
    
    @staticmethod
    async def _send_welcome_notification(db: Session, user_id: int, title: str, message: str):
        """사용자 로그인 시 환영 메시지 전송 (백그라운드 작업용)"""
        try:
            await NotificationService.send_notification(
                db=db,
                user_id=user_id,
                title=title,
                message=message,
                notif_type="hoş_geliş"
            )
            logger.info(f"Welcome notification sent to user {user_id}")
        except Exception as e:
            logger.error(f"Error sending welcome notification: {str(e)}")

    @staticmethod
    def award_referral_bonus(db: Session, referred_user_id: int, bonus_points: int = 100) -> None:
        """
        신규 사용자의 첫 걸음 기록 시, 추천인에게 referral_bonus 지급 및 알림 생성하고, 추천받은 사용자에게도 signup bonus 지급합니다.
        
        참고:
        - referred_user_id는 신규 사용자의 ID입니다.
        - 회원가입 시 이미 보너스가 지급된 경우(awarded=True)에는 중복 지급하지 않습니다.
        - 기존 엔드포인트 이름과 혼동하지 않기 위해 파라미터 이름은 유지했습니다.
        """
        # 신규 사용자(referred_user_id)가 referrer_user_id인 레코드를 찾는다
        # user_referrals 테이블에서 referrer_user_id = 신규 사용자 ID, awarded = False인 레코드 검색
        referral = db.query(UserReferral).filter(
            UserReferral.referrer_user_id == referred_user_id,
            UserReferral.awarded == False
        ).first()

        if referral:
            # 기존 사용자 ID
            existing_user_id = referral.referred_user_id
            
            # Award bonus to existing user
            point_tx = PointTransaction(
                user_id=existing_user_id,
                transaction_type="referral_bonus",
                amount=bonus_points,
                description="Referral bonus awarded upon new user's first step recording"
            )
            db.add(point_tx)

            existing_user_points = db.query(UserPoints).filter(UserPoints.user_id == existing_user_id).first()
            if existing_user_points:
                existing_user_points.total_points += bonus_points
            else:
                existing_user_points = UserPoints(user_id=existing_user_id, total_points=bonus_points)
                db.add(existing_user_points)

            # Award bonus to new user
            point_tx_new_user = PointTransaction(
                user_id=referred_user_id,  # 신규 사용자
                transaction_type="referral_bonus",
                amount=bonus_points,
                description="Signup bonus for using referral code"
            )
            db.add(point_tx_new_user)

            new_user_points = db.query(UserPoints).filter(UserPoints.user_id == referred_user_id).first()
            if new_user_points:
                new_user_points.total_points += bonus_points
            else:
                new_user_points = UserPoints(user_id=referred_user_id, total_points=bonus_points)
                db.add(new_user_points)

            # Mark as awarded to prevent duplicate bonus
            referral.awarded = True

            # Send notification to the existing user
            NotificationService.create_notification(
                db, 
                user_id=existing_user_id,
                notif_type="award_referral_point",
                title="Tebrikler! Referans Bonusu Kazandınız!",
                message=f"Arkadaşınızın ilk adım kaydı sayesinde {bonus_points} puan bonus kazandınız."
            )
            db.commit()

    @staticmethod
    def award_referral_chain_bonus(db: Session, referred_user_id: int, earned_points: int) -> None:
        """
        Referred user'ın kazandığı adım puanlarının belirli yüzdesini, 
        referral chain boyunca (0.5% ve sonrasında decay uygulanarak) her seviye için dağıtır.
        
        참고:
        - referred_user_id: 포인트를 획득한 사용자의 ID (신규 사용자)
        - 관계 방향: 신규 사용자가 referrer_user_id, 기존 사용자가 referred_user_id
        """
        # 사용자의 국가 코드 조회
        user = db.query(User).filter(User.id == referred_user_id).first()
        country_code = user.country_code if user else None
        
        # Admin settings에서 값 가져오기
        bonus_percentage = get_setting_float(db, "REFERRAL_DIRECT_BONUS_PERCENTAGE", country_code)
        max_level = get_setting_int(db, "REFERRAL_MAX_LEVEL", country_code)
        decay = get_setting_float(db, "REFERRAL_CHAIN_DECAY", country_code)
        
        level = 1
        current_user_id = referred_user_id

        while level <= max_level:
            # 현재 사용자가 referrer_user_id인 레코드 조회 (현재 사용자가 누구를 참조했는지)
            referral = db.query(UserReferral).filter(
                UserReferral.referrer_user_id == current_user_id
            ).first()
            
            if not referral:
                break
            
            # 참조된 사용자 ID (기존 사용자)
            existing_user_id = referral.referred_user_id

            # bonus 계산: earned_points * (bonus_percentage/100) * (decay ** (level - 1))
            bonus_points = int(earned_points * (bonus_percentage / 100) * (decay ** (level - 1)))
            
            if bonus_points > 0:
                # 기존 사용자에게 포인트 지급
                point_tx = PointTransaction(
                    user_id=existing_user_id,
                    transaction_type="referral_chain",
                    amount=bonus_points,
                    description=f"Referral chain bonus (level {level}) from user {referred_user_id} daily points."
                )
                db.add(point_tx)
                
                user_points_record = db.query(UserPoints).filter(UserPoints.user_id == existing_user_id).first()
                if user_points_record:
                    user_points_record.total_points += bonus_points
                else:
                    user_points_record = UserPoints(user_id=existing_user_id, total_points=bonus_points)
                    db.add(user_points_record)
            
            # 다음 레벨로 이동 - 참조된 사용자(기존 사용자)가 다음 current_user_id가 됨
            current_user_id = existing_user_id
            level += 1

        db.commit()

    # Added new method to fetch referral codes for all active users
    @staticmethod
    def get_all_referral_codes(db: Session) -> List[Dict]:
        try:
            referral_codes = db.query(User.id, User.reference_code).filter(User.status == 1).all()
            result = [{"id": row.id, "reference_code": row.reference_code} for row in referral_codes]
            return result
        except Exception as e:
            logger.error(f"Error fetching referral codes: {str(e)}", exc_info=True)
            raise

router = APIRouter()

class RefreshTokenRequest(BaseModel):
    refresh_token: str

class ForgotPasswordRequest(BaseModel):
    email: EmailStr

@router.post("/forgot-password")
def forgot_password(
    req: ForgotPasswordRequest,
    db: Session = Depends(get_db)
):
    user = UserService.get_user_by_email(db, req.email)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="User with provided email not found"
        )
    
    reset_code = generate_verification_code()
    reset_record = PasswordReset(user_id=user.id, reset_code=reset_code)
    db.add(reset_record)
    db.commit()
    
    if not send_verification_email(req.email, reset_code):
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Failed to send verification email"
        )
    
    return {"message": "Reset code has been sent to your email address."}

class VerifyResetCodeRequest(BaseModel):
    email: EmailStr
    reset_code: str

@router.post("/verify-reset-code")
def verify_reset_code(
    req: VerifyResetCodeRequest,
    db: Session = Depends(get_db)
):
    """
    Verifies the provided reset code for the given email.
    
    If verification succeeds:
      - Checks if the reset record is still valid and not expired.
      - If expired, marks it as used (is_valid = 0) and raises an error.
      - If valid, marks it as used to prevent re-use.
    """
    user = UserService.get_user_by_email(db, req.email)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, 
            detail="User not found"
        )
    
    reset_record = (
        db.query(PasswordReset)
        .filter(
            PasswordReset.user_id == user.id,
            PasswordReset.reset_code == req.reset_code
        )
        .order_by(PasswordReset.created_at.desc())
        .first()
    )
    
    if not reset_record:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, 
            detail="Invalid reset code"
        )
    
    # Debug log: record the creation time, expiry time, and current time
    expiry_time = reset_record.created_at + timedelta(minutes=10)
    current_time = now()
    logger.info(f"Reset record created_at: {reset_record.created_at}, expiry_time: {expiry_time}, current_time: {current_time}")
    
    # Check if the expiry time has passed (using the same function as when it was created)
    if current_time > expiry_time:
        reset_record.is_valid = 0
        db.commit()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, 
            detail="Reset code expired"
        )
    
    # If the code has already been used, it cannot be reused
    if reset_record.is_valid != 1:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, 
            detail="Reset code has already been used"
        )
    
    # Upon successful verification, prevent reuse by updating is_valid to 0
    reset_record.is_valid = 0
    db.commit()
    
    # Additional logic (e.g., password reset processing) is possible
    
    return {"message": "Reset code verified successfully"}
