import crypto from 'node:crypto';
import { config } from './config.js';
import { audit, getSetting, withTransaction } from './db.js';
import { queueEmail } from './email.js';
import { getOrCreateUser } from './game.js';
import { decryptJson, encryptJson, maskSecret, mergeSecretUpdates } from './secrets.js';
import { base64url, nowIso, nowSeconds, sanitizeText, sha256, uuid } from './utils.js';

function fail(code, message, status = 400, details = undefined) {
  throw Object.assign(new Error(message), { code, status, details });
}

function validEmail(input) {
  const email = String(input || '').trim().toLowerCase();
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) ? email : '';
}

function passwordPolicy(db, password) {
  const min = Math.max(10, Number(getSetting(db, 'account_password_min_length', 12)));
  if (String(password || '').length < min) fail('PASSWORD_WEAK', `Parola en az ${min} karakter olmalıdır.`, 400);
  if (!/[A-ZÇĞİÖŞÜ]/u.test(password) || !/[a-zçğıöşü]/u.test(password) || !/\d/.test(password)) {
    fail('PASSWORD_WEAK', 'Parola büyük harf, küçük harf ve rakam içermelidir.', 400);
  }
}

function passwordHash(password, salt = crypto.randomBytes(16), params = { N: 16384, r: 8, p: 1, dkLen: 32 }) {
  const derived = crypto.scryptSync(String(password), salt, params.dkLen, { N: params.N, r: params.r, p: params.p, maxmem: 64 * 1024 * 1024 });
  return { hash: derived.toString('base64url'), salt: salt.toString('base64url'), params };
}

function verifyPasswordHash(password, row) {
  const params = JSON.parse(row.password_params_json || '{}');
  const derived = passwordHash(password, Buffer.from(row.password_salt, 'base64url'), params).hash;
  const left = Buffer.from(derived);
  const right = Buffer.from(String(row.password_hash));
  return left.length === right.length && crypto.timingSafeEqual(left, right);
}

function publicUser(user) {
  return {
    id: user.id,
    email: user.email,
    emailVerifiedAt: user.email_verified_at,
    username: user.username,
    displayName: user.display_name,
    avatar: user.avatar,
    loginId: user.login_id,
    authPrimary: user.auth_primary,
    status: user.status,
    createdAt: user.created_at
  };
}

function issueEmailVerification(db, user) {
  const code = String(crypto.randomInt(100000, 1000000));
  const now = nowIso();
  db.prepare('UPDATE email_verification_tokens SET consumed_at=? WHERE user_id=? AND consumed_at IS NULL').run(now, user.id);
  const ttlMinutes = Math.max(5, Number(getSetting(db, 'email_verification_ttl_minutes', 15)) || 15);
  db.prepare('INSERT INTO email_verification_tokens(id,user_id,code_hash,expires_at,created_at) VALUES(?,?,?,?,?)')
    .run(uuid(), user.id, sha256(code), nowSeconds() + ttlMinutes * 60, now);
  const queued = queueEmail(db, {
    to: user.email,
    subject: 'OnlyCoin e-posta doğrulama kodu',
    template: 'email-verification',
    text: `OnlyCoin doğrulama kodunuz: ${code}\n\nKod 15 dakika geçerlidir. Bu işlemi siz yapmadıysanız e-postayı dikkate almayın.`,
    html: `<p>OnlyCoin doğrulama kodunuz:</p><p style="font-size:30px;font-weight:bold;letter-spacing:5px">${code}</p><p>Kod 15 dakika geçerlidir.</p>`,
    metadata: { userId: user.id }
  });
  return { code, emailId: queued?.id || '' };
}

export function registerWithEmail(db, input = {}) {
  if (!Boolean(getSetting(db, 'email_registration_enabled', getSetting(db, 'account_registration_enabled', true)))) fail('EMAIL_REGISTRATION_DISABLED', 'E-posta ile kayıt kapalıdır.', 403);
  const email = validEmail(input.email);
  if (!email) fail('INVALID_EMAIL', 'Geçerli bir e-posta adresi girin.', 400);
  passwordPolicy(db, input.password);
  if (String(input.password || '') !== String(input.confirmPassword || '')) fail('PASSWORD_MISMATCH', 'Parolalar eşleşmiyor.', 400);
  const existing = db.prepare('SELECT * FROM users WHERE lower(email)=?').get(email);
  if (existing) fail('EMAIL_ALREADY_REGISTERED', 'Bu e-posta ile bir hesap zaten bulunuyor.', 409);
  const usernameBase = sanitizeText(input.username || email.split('@')[0], 32).replace(/[^\p{L}\p{N}_-]/gu, '') || `member_${crypto.randomBytes(3).toString('hex')}`;
  const user = getOrCreateUser(db, {
    externalId: `email:${email}`,
    username: usernameBase,
    displayName: sanitizeText(input.displayName || usernameBase, 60),
    avatar: '',
    referralCode: sanitizeText(input.referralCode || '', 20)
  });
  const hashed = passwordHash(input.password);
  const now = nowIso();
  withTransaction(db, () => {
    db.prepare("UPDATE users SET email=?,auth_primary='email',updated_at=? WHERE id=?").run(email, now, user.id);
    db.prepare(`INSERT INTO user_credentials(user_id,password_hash,password_salt,password_params_json,password_changed_at,created_at,updated_at)
      VALUES(?,?,?,?,?,?,?)`).run(user.id, hashed.hash, hashed.salt, JSON.stringify(hashed.params), now, now, now);
  });
  const after = db.prepare('SELECT * FROM users WHERE id=?').get(user.id);
  const verification = issueEmailVerification(db, after);
  return { user: publicUser(after), verificationRequired: Boolean(getSetting(db, 'email_verification_required', getSetting(db, 'account_require_email_verification', false))), emailDeliveryId: verification.emailId, ...(config.environment === 'test' ? { debugVerificationCode: verification.code } : {}) };
}

export function verifyEmail(db, input = {}) {
  const email = validEmail(input.email);
  const user = email ? db.prepare('SELECT * FROM users WHERE lower(email)=?').get(email) : db.prepare('SELECT * FROM users WHERE id=?').get(String(input.userId || ''));
  if (!user) fail('USER_NOT_FOUND', 'Kullanıcı bulunamadı.', 404);
  if (user.email_verified_at) return { user: publicUser(user), alreadyVerified: true };
  const token = db.prepare('SELECT * FROM email_verification_tokens WHERE user_id=? AND consumed_at IS NULL ORDER BY created_at DESC LIMIT 1').get(user.id);
  if (!token) fail('VERIFICATION_NOT_FOUND', 'Doğrulama isteği bulunamadı.', 404);
  if (token.expires_at <= nowSeconds()) fail('VERIFICATION_EXPIRED', 'Doğrulama kodunun süresi doldu.', 410);
  const maxAttempts = Math.max(3, Number(getSetting(db, 'email_verification_max_attempts', 5)) || 5);
  if (token.attempts >= maxAttempts) fail('VERIFICATION_ATTEMPTS_EXCEEDED', 'Doğrulama deneme sınırı aşıldı. Yeni kod isteyin.', 429);
  if (sha256(String(input.code || '').trim()) !== token.code_hash) {
    db.prepare('UPDATE email_verification_tokens SET attempts=attempts+1 WHERE id=?').run(token.id);
    fail('VERIFICATION_CODE_INVALID', 'Doğrulama kodu geçersiz.', 400, { remainingAttempts: Math.max(0, maxAttempts - token.attempts - 1) });
  }
  const now = nowIso();
  withTransaction(db, () => {
    db.prepare('UPDATE email_verification_tokens SET consumed_at=? WHERE id=?').run(now, token.id);
    db.prepare('UPDATE users SET email_verified_at=?,updated_at=? WHERE id=?').run(now, now, user.id);
  });
  return { user: publicUser(db.prepare('SELECT * FROM users WHERE id=?').get(user.id)) };
}

export function resendEmailVerification(db, emailInput) {
  const email = validEmail(emailInput);
  if (!email) fail('INVALID_EMAIL', 'Geçerli bir e-posta adresi girin.', 400);
  const user = db.prepare('SELECT * FROM users WHERE lower(email)=?').get(email);
  if (!user) return { queued: true };
  if (user.email_verified_at) return { queued: true, alreadyVerified: true };
  const recent = db.prepare("SELECT created_at FROM email_verification_tokens WHERE user_id=? ORDER BY created_at DESC LIMIT 1").get(user.id);
  if (recent && Date.now() - new Date(recent.created_at).getTime() < 60_000) fail('VERIFICATION_RATE_LIMIT', 'Yeni kod istemeden önce bir dakika bekleyin.', 429);
  const verification = issueEmailVerification(db, user);
  return { queued: true, emailDeliveryId: verification.emailId, ...(config.environment === 'test' ? { debugVerificationCode: verification.code } : {}) };
}

export function loginWithEmail(db, input = {}) {
  const email = validEmail(input.email);
  if (!email) fail('INVALID_CREDENTIALS', 'E-posta veya parola hatalı.', 401);
  const user = db.prepare('SELECT * FROM users WHERE lower(email)=?').get(email);
  if (!user || user.status === 'deleted') fail('INVALID_CREDENTIALS', 'E-posta veya parola hatalı.', 401);
  if (user.status === 'blocked') fail('ACCOUNT_BLOCKED', 'Hesabınız geçici olarak durdurulmuştur.', 403);
  const credential = db.prepare('SELECT * FROM user_credentials WHERE user_id=?').get(user.id);
  if (!credential) fail('PASSWORD_LOGIN_UNAVAILABLE', 'Bu hesap sosyal giriş ile oluşturulmuş. Bağlı giriş yöntemini kullanın.', 409);
  if (credential.locked_until && credential.locked_until > nowSeconds()) fail('ACCOUNT_TEMPORARILY_LOCKED', 'Çok sayıda hatalı deneme nedeniyle giriş geçici olarak kilitlendi.', 429);
  if (!verifyPasswordHash(input.password, credential)) {
    const attempts = credential.failed_attempts + 1;
    const lockedUntil = attempts >= 5 ? nowSeconds() + Math.min(3600, 30 * 2 ** Math.min(attempts - 5, 6)) : null;
    db.prepare('UPDATE user_credentials SET failed_attempts=?,locked_until=?,updated_at=? WHERE user_id=?').run(attempts, lockedUntil, nowIso(), user.id);
    fail('INVALID_CREDENTIALS', 'E-posta veya parola hatalı.', 401);
  }
  if (Boolean(getSetting(db, 'email_verification_required', getSetting(db, 'account_require_email_verification', false))) && !user.email_verified_at) fail('EMAIL_NOT_VERIFIED', 'Giriş için e-posta adresinizi doğrulayın.', 403);
  db.prepare('UPDATE user_credentials SET failed_attempts=0,locked_until=NULL,updated_at=? WHERE user_id=?').run(nowIso(), user.id);
  db.prepare('UPDATE users SET last_login_at=?,updated_at=? WHERE id=?').run(nowIso(), nowIso(), user.id);
  return { user: publicUser(db.prepare('SELECT * FROM users WHERE id=?').get(user.id)) };
}

export function requestPasswordReset(db, emailInput) {
  const email = validEmail(emailInput);
  if (!email) return { queued: true };
  const user = db.prepare('SELECT * FROM users WHERE lower(email)=? AND status<>?').get(email, 'deleted');
  if (!user) return { queued: true };
  const raw = crypto.randomBytes(32).toString('base64url');
  const ttl = Math.max(10, Number(getSetting(db, 'password_reset_ttl_minutes', 30)) || 30);
  const now = nowIso();
  db.prepare('UPDATE password_reset_tokens SET consumed_at=? WHERE user_id=? AND consumed_at IS NULL').run(now, user.id);
  db.prepare('INSERT INTO password_reset_tokens(id,user_id,token_hash,expires_at,created_at) VALUES(?,?,?,?,?)')
    .run(uuid(), user.id, sha256(raw), nowSeconds() + ttl * 60, now);
  const url = `${config.appOrigin}/game/?reset_token=${encodeURIComponent(raw)}&email=${encodeURIComponent(email)}`;
  queueEmail(db, {
    to: email,
    subject: 'OnlyCoin parola yenileme bağlantısı',
    template: 'password-reset',
    text: `OnlyCoin parolanızı yenilemek için bağlantıyı açın:\n${url}\n\nBağlantı ${ttl} dakika geçerlidir.`,
    html: `<p>OnlyCoin parolanızı yenilemek için aşağıdaki bağlantıyı açın:</p><p><a href="${url}">Parolamı Yenile</a></p><p>Bağlantı ${ttl} dakika geçerlidir.</p>`,
    metadata: { userId: user.id }
  });
  return { queued: true, ...(config.environment === 'test' ? { debugResetToken: raw } : {}) };
}

export function resetPassword(db, input = {}) {
  const tokenHash = sha256(String(input.token || ''));
  const row = db.prepare('SELECT * FROM password_reset_tokens WHERE token_hash=?').get(tokenHash);
  if (!row) fail('PASSWORD_RESET_INVALID', 'Parola yenileme bağlantısı geçersiz.', 400);
  if (row.consumed_at) fail('PASSWORD_RESET_USED', 'Parola yenileme bağlantısı daha önce kullanılmış.', 409);
  if (row.expires_at <= nowSeconds()) fail('PASSWORD_RESET_EXPIRED', 'Parola yenileme bağlantısının süresi dolmuş.', 410);
  passwordPolicy(db, input.password);
  if (String(input.password || '') !== String(input.confirmPassword || '')) fail('PASSWORD_MISMATCH', 'Parolalar eşleşmiyor.', 400);
  const hashed = passwordHash(input.password);
  const now = nowIso();
  withTransaction(db, () => {
    db.prepare(`UPDATE user_credentials SET password_hash=?,password_salt=?,password_params_json=?,failed_attempts=0,locked_until=NULL,password_changed_at=?,updated_at=? WHERE user_id=?`)
      .run(hashed.hash, hashed.salt, JSON.stringify(hashed.params), now, now, row.user_id);
    db.prepare('UPDATE password_reset_tokens SET consumed_at=? WHERE id=?').run(now, row.id);
    db.prepare('DELETE FROM sessions WHERE user_id=?').run(row.user_id);
  });
  return { changed: true };
}

function providerRow(row, includeSecrets = false) {
  const secrets = decryptJson(row.secrets_enc, {});
  return {
    id: row.id,
    name: row.name,
    enabled: Boolean(row.enabled),
    clientId: row.client_id,
    authorizeUrl: row.authorize_url,
    tokenUrl: row.token_url,
    profileUrl: row.profile_url,
    jwksUrl: row.jwks_url,
    issuer: row.issuer,
    scopes: row.scopes,
    config: JSON.parse(row.config_json || '{}'),
    sortOrder: row.sort_order,
    secrets: includeSecrets ? Object.fromEntries(Object.entries(secrets).map(([k, v]) => [k, maskSecret(v)])) : undefined,
    configured: Boolean(row.client_id && (secrets.clientSecret || (row.id === 'apple' && secrets.privateKey && secrets.teamId && secrets.keyId)))
  };
}

export function publicAuthConfig(db) {
  const enabled = Boolean(getSetting(db, 'social_login_enabled', getSetting(db, 'oauth_enabled', true)));
  const providers = enabled ? db.prepare('SELECT * FROM oauth_providers WHERE enabled=1 ORDER BY sort_order,name').all().map((row) => providerRow(row)) : [];
  return {
    emailRegistrationEnabled: Boolean(getSetting(db, 'email_registration_enabled', getSetting(db, 'account_registration_enabled', true))),
    emailVerificationRequired: Boolean(getSetting(db, 'email_verification_required', getSetting(db, 'account_require_email_verification', false))),
    socialLoginEnabled: enabled,
    providers: providers.filter((provider) => provider.configured).map(({ secrets, ...provider }) => provider)
  };
}

export function adminAuthProviders(db) {
  return db.prepare('SELECT * FROM oauth_providers ORDER BY sort_order,name').all().map((row) => providerRow(row, true));
}

export function adminUpdateAuthProvider(db, providerId, values, actor = 'admin', ip = '') {
  return withTransaction(db, () => {
    const before = db.prepare('SELECT * FROM oauth_providers WHERE id=?').get(providerId);
    if (!before) fail('OAUTH_PROVIDER_NOT_FOUND', 'Sosyal giriş sağlayıcısı bulunamadı.', 404);
    const currentSecrets = decryptJson(before.secrets_enc, {});
    const secrets = mergeSecretUpdates(currentSecrets, values.secrets || {});
    const configJson = { ...JSON.parse(before.config_json || '{}'), ...(values.config || {}) };
    db.prepare(`UPDATE oauth_providers SET name=?,enabled=?,client_id=?,secrets_enc=?,authorize_url=?,token_url=?,profile_url=?,jwks_url=?,issuer=?,scopes=?,config_json=?,sort_order=?,updated_at=? WHERE id=?`)
      .run(sanitizeText(values.name ?? before.name, 60), values.enabled ? 1 : 0, sanitizeText(values.clientId ?? before.client_id, 300), encryptJson(secrets), sanitizeText(values.authorizeUrl ?? before.authorize_url, 500), sanitizeText(values.tokenUrl ?? before.token_url, 500), sanitizeText(values.profileUrl ?? before.profile_url, 500), sanitizeText(values.jwksUrl ?? before.jwks_url, 500), sanitizeText(values.issuer ?? before.issuer, 200), sanitizeText(values.scopes ?? before.scopes, 300), JSON.stringify(configJson), Number(values.sortOrder ?? before.sort_order), nowIso(), providerId);
    const after = db.prepare('SELECT * FROM oauth_providers WHERE id=?').get(providerId);
    audit(db, { actorType: 'admin', actorId: actor, action: 'oauth.provider.update', targetType: 'oauth_provider', targetId: providerId, before: providerRow(before, true), after: providerRow(after, true), ip });
    return providerRow(after, true);
  });
}

function codeChallenge(verifier) {
  return crypto.createHash('sha256').update(verifier).digest('base64url');
}

function oauthRedirectUri(providerId) {
  return `${config.appOrigin}/api/auth/oauth/${encodeURIComponent(providerId)}/callback`;
}

export function startOAuth(db, providerId, options = {}) {
  if (!Boolean(getSetting(db, 'social_login_enabled', getSetting(db, 'oauth_enabled', true)))) fail('SOCIAL_LOGIN_DISABLED', 'Sosyal giriş kapalıdır.', 403);
  const row = db.prepare('SELECT * FROM oauth_providers WHERE id=? AND enabled=1').get(providerId);
  if (!row) fail('OAUTH_PROVIDER_DISABLED', 'Bu sosyal giriş yöntemi aktif değil.', 404);
  const provider = providerRow(row);
  if (!provider.configured) fail('OAUTH_PROVIDER_NOT_CONFIGURED', 'Bu sosyal giriş yöntemi henüz yapılandırılmamış.', 503);
  const state = crypto.randomBytes(32).toString('base64url');
  const verifier = crypto.randomBytes(48).toString('base64url');
  const nonce = crypto.randomBytes(24).toString('base64url');
  const configJson = provider.config || {};
  db.prepare(`INSERT INTO oauth_states(id,provider,state_hash,code_verifier,nonce,redirect_after,referral_code,expires_at,created_at)
    VALUES(?,?,?,?,?,?,?,?,?)`).run(uuid(), providerId, sha256(state), configJson.pkce === false ? '' : verifier, nonce, sanitizeText(options.redirectAfter || '/game/', 300), sanitizeText(options.referralCode || '', 20), nowSeconds() + 10 * 60, nowIso());
  const url = new URL(row.authorize_url);
  url.searchParams.set('client_id', row.client_id);
  url.searchParams.set('redirect_uri', oauthRedirectUri(providerId));
  url.searchParams.set('response_type', 'code');
  url.searchParams.set('scope', row.scopes || 'openid email profile');
  url.searchParams.set('state', state);
  if (providerId === 'google') {
    url.searchParams.set('nonce', nonce);
    url.searchParams.set('access_type', 'online');
    url.searchParams.set('include_granted_scopes', 'true');
  }
  if (providerId === 'apple') {
    url.searchParams.set('response_mode', configJson.responseMode || 'form_post');
    url.searchParams.set('nonce', nonce);
  }
  if (configJson.pkce !== false) {
    url.searchParams.set('code_challenge', codeChallenge(verifier));
    url.searchParams.set('code_challenge_method', 'S256');
  }
  return { url: url.toString(), provider: { id: provider.id, name: provider.name } };
}

function base64urlJson(part) {
  try { return JSON.parse(Buffer.from(part, 'base64url').toString('utf8')); } catch { return null; }
}

async function verifyJwtWithJwks(token, { jwksUrl, issuer, audience, nonce = '' }) {
  const parts = String(token || '').split('.');
  if (parts.length !== 3) fail('OAUTH_ID_TOKEN_INVALID', 'Kimlik belirteci geçersiz.', 401);
  const header = base64urlJson(parts[0]);
  const payload = base64urlJson(parts[1]);
  if (!header || !payload || header.alg !== 'RS256') fail('OAUTH_ID_TOKEN_INVALID', 'Kimlik belirteci algoritması geçersiz.', 401);
  const response = await fetch(jwksUrl, { signal: AbortSignal.timeout(10000) });
  if (!response.ok) fail('OAUTH_JWKS_FAILED', 'Kimlik sağlayıcısının anahtarları alınamadı.', 502);
  const jwks = await response.json();
  const jwk = (jwks.keys || []).find((key) => key.kid === header.kid && key.kty === 'RSA');
  if (!jwk) fail('OAUTH_JWK_NOT_FOUND', 'Kimlik sağlayıcısı doğrulama anahtarı bulunamadı.', 502);
  const key = crypto.createPublicKey({ key: jwk, format: 'jwk' });
  const valid = crypto.verify('RSA-SHA256', Buffer.from(`${parts[0]}.${parts[1]}`), key, Buffer.from(parts[2], 'base64url'));
  if (!valid) fail('OAUTH_ID_TOKEN_SIGNATURE_INVALID', 'Kimlik belirteci imzası doğrulanamadı.', 401);
  if (issuer && payload.iss !== issuer) fail('OAUTH_ISSUER_INVALID', 'Kimlik sağlayıcısı doğrulanamadı.', 401);
  const audiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
  if (audience && !audiences.includes(audience)) fail('OAUTH_AUDIENCE_INVALID', 'Kimlik belirteci bu uygulama için oluşturulmamış.', 401);
  if (!payload.exp || payload.exp <= nowSeconds()) fail('OAUTH_ID_TOKEN_EXPIRED', 'Kimlik belirtecinin süresi dolmuş.', 401);
  if (nonce && payload.nonce && payload.nonce !== nonce) fail('OAUTH_NONCE_INVALID', 'Kimlik doğrulama nonce değeri eşleşmiyor.', 401);
  return payload;
}

function appleClientSecret(row, secrets) {
  if (secrets.clientSecret) return secrets.clientSecret;
  if (!secrets.teamId || !secrets.keyId || !secrets.privateKey) fail('APPLE_SECRET_NOT_CONFIGURED', 'Apple Team ID, Key ID ve private key tanımlanmalıdır.', 503);
  const header = base64url(JSON.stringify({ alg: 'ES256', kid: secrets.keyId, typ: 'JWT' }));
  const now = nowSeconds();
  const body = base64url(JSON.stringify({ iss: secrets.teamId, iat: now, exp: now + 15552000, aud: 'https://appleid.apple.com', sub: row.client_id }));
  const signature = crypto.sign('sha256', Buffer.from(`${header}.${body}`), { key: secrets.privateKey.replace(/\\n/g, '\n'), dsaEncoding: 'ieee-p1363' }).toString('base64url');
  return `${header}.${body}.${signature}`;
}

async function exchangeOAuthCode(row, stateRow, code) {
  const secrets = decryptJson(row.secrets_enc, {});
  const params = new URLSearchParams({
    grant_type: 'authorization_code',
    code,
    redirect_uri: oauthRedirectUri(row.id),
    client_id: row.client_id
  });
  if (stateRow.code_verifier) params.set('code_verifier', stateRow.code_verifier);
  if (row.id === 'apple') params.set('client_secret', appleClientSecret(row, secrets));
  else if (secrets.clientSecret) params.set('client_secret', secrets.clientSecret);
  const headers = { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' };
  if (row.id === 'x' && secrets.clientSecret) headers.Authorization = `Basic ${Buffer.from(`${row.client_id}:${secrets.clientSecret}`).toString('base64')}`;
  const response = await fetch(row.token_url, { method: 'POST', headers, body: params, signal: AbortSignal.timeout(15000) });
  const text = await response.text();
  let data;
  try { data = JSON.parse(text); } catch { data = Object.fromEntries(new URLSearchParams(text)); }
  if (!response.ok || data.error) fail('OAUTH_TOKEN_EXCHANGE_FAILED', 'Sosyal giriş belirteci alınamadı.', 502, { provider: row.id, error: data.error_description || data.error || text.slice(0, 200) });
  return data;
}

async function oauthProfile(row, stateRow, tokenData, callbackBody = {}) {
  if (row.id === 'apple') {
    const claims = await verifyJwtWithJwks(tokenData.id_token, { jwksUrl: row.jwks_url, issuer: row.issuer, audience: row.client_id, nonce: stateRow.nonce });
    let postedUser = {};
    try { postedUser = typeof callbackBody.user === 'string' ? JSON.parse(callbackBody.user) : (callbackBody.user || {}); } catch { /* ignore */ }
    return { providerUserId: claims.sub, email: claims.email || '', emailVerified: claims.email_verified === true || claims.email_verified === 'true', name: [postedUser.name?.firstName, postedUser.name?.lastName].filter(Boolean).join(' ') || claims.email?.split('@')[0] || 'Apple Üyesi', username: `apple_${String(claims.sub).slice(-10)}`, avatar: '', raw: claims };
  }
  if (!tokenData.access_token) fail('OAUTH_ACCESS_TOKEN_MISSING', 'Sosyal giriş erişim belirteci alınamadı.', 502);
  const response = await fetch(row.profile_url, { headers: { Authorization: `Bearer ${tokenData.access_token}`, Accept: 'application/json' }, signal: AbortSignal.timeout(10000) });
  const data = await response.json();
  if (!response.ok || data.error) fail('OAUTH_PROFILE_FAILED', 'Sosyal profil bilgileri alınamadı.', 502);
  if (row.id === 'x') {
    const p = data.data || data;
    return { providerUserId: String(p.id), email: '', emailVerified: false, name: p.name || p.username || 'X Üyesi', username: p.username || `x_${p.id}`, avatar: p.profile_image_url || '', raw: p };
  }
  if (row.id === 'facebook') {
    return { providerUserId: String(data.id), email: data.email || '', emailVerified: Boolean(data.email), name: data.name || 'Facebook Üyesi', username: `fb_${data.id}`, avatar: data.picture?.data?.url || '', raw: data };
  }
  return { providerUserId: String(data.sub || data.id), email: data.email || '', emailVerified: Boolean(data.email_verified), name: data.name || data.given_name || data.email?.split('@')[0] || 'Google Üyesi', username: data.email?.split('@')[0] || `google_${data.sub || data.id}`, avatar: data.picture || '', raw: data };
}

function findOrCreateOAuthUser(db, row, profile, referralCode) {
  const identity = db.prepare('SELECT * FROM user_oauth_identities WHERE provider=? AND provider_user_id=?').get(row.id, profile.providerUserId);
  if (identity) return db.prepare('SELECT * FROM users WHERE id=?').get(identity.user_id);
  const email = validEmail(profile.email);
  let user = email ? db.prepare('SELECT * FROM users WHERE lower(email)=?').get(email) : null;
  if (!user) {
    user = getOrCreateUser(db, {
      externalId: `oauth:${row.id}:${profile.providerUserId}`,
      username: sanitizeText(profile.username, 32),
      displayName: sanitizeText(profile.name, 60),
      avatar: sanitizeText(profile.avatar, 500),
      referralCode
    });
  }
  const now = nowIso();
  withTransaction(db, () => {
    if (email && !user.email) db.prepare('UPDATE users SET email=?,email_verified_at=?,auth_primary=?,updated_at=? WHERE id=?').run(email, profile.emailVerified ? now : null, row.id, now, user.id);
    else db.prepare('UPDATE users SET auth_primary=CASE WHEN auth_primary=? THEN ? ELSE auth_primary END,last_login_at=?,updated_at=? WHERE id=?').run('telegram', row.id, now, now, user.id);
    db.prepare(`INSERT INTO user_oauth_identities(id,user_id,provider,provider_user_id,email,email_verified,profile_json,created_at,updated_at)
      VALUES(?,?,?,?,?,?,?,?,?)`).run(uuid(), user.id, row.id, profile.providerUserId, email, profile.emailVerified ? 1 : 0, JSON.stringify(profile.raw || {}), now, now);
  });
  return db.prepare('SELECT * FROM users WHERE id=?').get(user.id);
}

function createExchangeCode(db, userId) {
  const code = crypto.randomBytes(32).toString('base64url');
  db.prepare('INSERT INTO login_exchange_codes(id,user_id,code_hash,expires_at,created_at) VALUES(?,?,?,?,?)')
    .run(uuid(), userId, sha256(code), nowSeconds() + 120, nowIso());
  return code;
}

export async function completeOAuth(db, providerId, callback = {}) {
  const stateValue = String(callback.state || '');
  const stateRow = db.prepare('SELECT * FROM oauth_states WHERE provider=? AND state_hash=?').get(providerId, sha256(stateValue));
  if (!stateRow) fail('OAUTH_STATE_INVALID', 'Sosyal giriş oturumu geçersiz.', 401);
  if (stateRow.expires_at <= nowSeconds()) fail('OAUTH_STATE_EXPIRED', 'Sosyal giriş oturumunun süresi doldu.', 410);
  if (callback.error) fail('OAUTH_PROVIDER_ERROR', sanitizeText(callback.error_description || callback.error, 300), 401);
  const code = String(callback.code || '');
  if (!code) fail('OAUTH_CODE_MISSING', 'Sosyal giriş kodu alınamadı.', 400);
  const row = db.prepare('SELECT * FROM oauth_providers WHERE id=? AND enabled=1').get(providerId);
  if (!row) fail('OAUTH_PROVIDER_DISABLED', 'Sosyal giriş yöntemi aktif değil.', 404);
  const tokenData = await exchangeOAuthCode(row, stateRow, code);
  const profile = await oauthProfile(row, stateRow, tokenData, callback);
  const user = findOrCreateOAuthUser(db, row, profile, stateRow.referral_code);
  db.prepare('DELETE FROM oauth_states WHERE id=?').run(stateRow.id);
  const loginCode = createExchangeCode(db, user.id);
  return { user: publicUser(user), loginCode, redirectUrl: `${stateRow.redirect_after || '/game/'}${(stateRow.redirect_after || '/game/').includes('?') ? '&' : '?'}login_code=${encodeURIComponent(loginCode)}` };
}

export function exchangeLoginCode(db, code) {
  return withTransaction(db, () => {
    const row = db.prepare('SELECT * FROM login_exchange_codes WHERE code_hash=?').get(sha256(String(code || '')));
    if (!row) fail('LOGIN_CODE_INVALID', 'Giriş kodu geçersiz.', 401);
    if (row.consumed_at) fail('LOGIN_CODE_USED', 'Giriş kodu daha önce kullanılmış.', 409);
    if (row.expires_at <= nowSeconds()) fail('LOGIN_CODE_EXPIRED', 'Giriş kodunun süresi dolmuş.', 410);
    db.prepare('UPDATE login_exchange_codes SET consumed_at=? WHERE id=?').run(nowIso(), row.id);
    const user = db.prepare('SELECT * FROM users WHERE id=?').get(row.user_id);
    if (!user) fail('USER_NOT_FOUND', 'Kullanıcı bulunamadı.', 404);
    return publicUser(user);
  });
}
