from datetime import datetime, timedelta, timezone import uuid from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, text, cast, String from app.models.identity import User, Person, UserRole, VerificationToken, Wallet from app.models.organization import Organization from app.schemas.auth import UserLiteRegister, UserKYCComplete from app.core.security import get_password_hash, verify_password from app.services.email_manager import email_manager from app.core.config import settings from sqlalchemy.orm import joinedload # <--- EZT ADD HOZZÁ AZ IMPORTOKHOZ! class AuthService: @staticmethod async def register_lite(db: AsyncSession, user_in: UserLiteRegister): """Step 1: Lite regisztráció + Token generálás + Email.""" try: # 1. Person alap létrehozása new_person = Person( first_name=user_in.first_name, last_name=user_in.last_name, is_active=False ) db.add(new_person) await db.flush() # 2. User technikai fiók new_user = User( email=user_in.email, hashed_password=get_password_hash(user_in.password), person_id=new_person.id, role=UserRole.user, is_active=False, region_code=user_in.region_code ) db.add(new_user) await db.flush() # 3. Kormányozható Token generálása expire_hours = getattr(settings, "REGISTRATION_TOKEN_EXPIRE_HOURS", 48) token_val = uuid.uuid4() new_token = VerificationToken( token=token_val, user_id=new_user.id, token_type="registration", expires_at=datetime.now(timezone.utc) + timedelta(hours=expire_hours) ) db.add(new_token) await db.flush() # 4. Email küldés gombbal verification_link = f"{settings.FRONTEND_BASE_URL}/verify?token={token_val}" try: await email_manager.send_email( recipient=user_in.email, template_key="registration", variables={ "first_name": user_in.first_name, "link": verification_link } ) except Exception as email_err: print(f"CRITICAL: Email failed: {str(email_err)}") await db.commit() await db.refresh(new_user) return new_user except Exception as e: await db.rollback() raise e @staticmethod async def verify_email(db: AsyncSession, token_str: str): """Token ellenőrzése (Email megerősítés).""" try: token_uuid = uuid.UUID(token_str) stmt = select(VerificationToken).where( VerificationToken.token == token_uuid, VerificationToken.is_used == False, VerificationToken.expires_at > datetime.now(timezone.utc) ) result = await db.execute(stmt) token_obj = result.scalar_one_or_none() if not token_obj: return False token_obj.is_used = True await db.commit() return True except Exception as e: print(f"Verify error: {e}") await db.rollback() return False @staticmethod async def complete_kyc(db: AsyncSession, user_id: int, kyc_in: UserKYCComplete): """Step 2: KYC adatok rögzítése JSON-biztos dátumkezeléssel.""" try: # 1. User és Person lekérése joinedload-dal (a korábbi hiba javítása) stmt = ( select(User) .options(joinedload(User.person)) .where(User.id == user_id) ) result = await db.execute(stmt) user = result.scalar_one_or_none() if not user or not user.person: return None # 2. Előkészítjük a JSON-kompatibilis adatokat # A mode='json' átalakítja a date objektumokat string-gé! kyc_data_json = kyc_in.model_dump(mode='json') p = user.person p.phone = kyc_in.phone_number p.birth_place = kyc_in.birth_place # A sima DATE oszlopba mehet a Python date objektum p.birth_date = datetime.combine(kyc_in.birth_date, datetime.min.time()) p.mothers_name = kyc_in.mothers_name # A JSONB mezőkbe a már stringesített adatokat tesszük p.identity_docs = kyc_data_json["identity_docs"] p.ice_contact = kyc_data_json["ice_contact"] p.is_active = True # 3. PRIVÁT FLOTTA (Organization) # Megnézzük, létezik-e már (idempotencia) org_stmt = select(Organization).where( Organization.owner_id == user.id, cast(Organization.org_type, String) == "individual" ) org_res = await db.execute(org_stmt) existing_org = org_res.scalar_one_or_none() if not existing_org: new_org = Organization( name=f"{p.last_name} {p.first_name} - Privát Flotta", owner_id=user.id, is_active=True, org_type="individual", is_verified=True, is_transferable=True ) db.add(new_org) # 4. WALLET wallet_stmt = select(Wallet).where(Wallet.user_id == user.id) wallet_res = await db.execute(wallet_stmt) if not wallet_res.scalar_one_or_none(): new_wallet = Wallet(user_id=user.id, coin_balance=0.0, xp_balance=0) db.add(new_wallet) # 5. USER AKTIVÁLÁSA user.is_active = True await db.commit() await db.refresh(user) return user except Exception as e: await db.rollback() print(f"CRITICAL KYC ERROR: {str(e)}") raise e @staticmethod async def authenticate(db: AsyncSession, email: str, password: str): stmt = select(User).where(User.email == email, User.is_deleted == False) res = await db.execute(stmt) user = res.scalar_one_or_none() if not user or not user.hashed_password or not verify_password(password, user.hashed_password): return None return user @staticmethod async def initiate_password_reset(db: AsyncSession, email: str): """Jelszó-visszaállítás indítása.""" stmt = select(User).where(User.email == email, User.is_deleted == False) res = await db.execute(stmt) user = res.scalar_one_or_none() if user: expire_hours = getattr(settings, "PASSWORD_RESET_TOKEN_EXPIRE_HOURS", 1) token_val = uuid.uuid4() new_token = VerificationToken( token=token_val, user_id=user.id, token_type="password_reset", expires_at=datetime.now(timezone.utc) + timedelta(hours=expire_hours) ) db.add(new_token) reset_link = f"{settings.FRONTEND_BASE_URL}/reset-password?token={token_val}" await email_manager.send_email( recipient=email, template_key="password_reset", variables={"link": reset_link}, user_id=user.id ) await db.commit() return True return False