import { describe, it, expect, beforeEach, vi } from 'vitest';
import { prisma } from '@/lib/db';
import { issueToken, consumeToken, peekToken } from '@/lib/tokens';
import { verifyEmail, resetEmail } from '@/lib/emails';
import { resetDb, mkUser } from './helpers';

// Server action bağımlılıkları
const sent: { to: string; subject: string; html: string }[] = [];
const cookieJar = new Map<string, string>();
vi.mock('next/headers', () => ({
  headers: async () => new Headers({ 'x-forwarded-for': '203.0.113.' + Math.floor(Math.random() * 200), host: 'test.local' }),
  cookies: async () => ({ get: (k: string) => (cookieJar.has(k) ? { value: cookieJar.get(k) } : undefined), set: (k: string, v: string) => cookieJar.set(k, v), delete: (k: string) => cookieJar.delete(k) }),
}));
vi.mock('next/navigation', () => ({ redirect: (u: string) => { throw Object.assign(new Error('REDIRECT'), { url: u }); } }));
vi.mock('@/lib/notify', () => ({ sendEmail: async (to: string, subject: string, html: string) => { sent.push({ to, subject, html }); } }));

const { requestReset, resetPassword } = await import('@/app/(auth)/actions');
const { verifyPassword, hashPassword } = await import('@/lib/auth');
const fd = (o: Record<string, string>) => { const f = new FormData(); for (const [k, v] of Object.entries(o)) f.set(k, v); return f; };
const tokenFrom = (html: string) => html.match(/token=([A-Za-z0-9_-]+)/)![1];

beforeEach(async () => { await resetDb(); sent.length = 0; cookieJar.clear(); });

describe('jetonlar', () => {
  it('tek kullanımlık ve türüne bağlı', async () => {
    const u = await mkUser(); const t = await issueToken(u.id, 'verify');
    expect(await peekToken(t, 'verify')).toBe(true);
    expect(await consumeToken(t, 'reset')).toBeNull();
    expect(await consumeToken(t, 'verify')).toBe(u.id);
    expect(await consumeToken(t, 'verify')).toBeNull();
  });
  it('veritabanında düz metin saklanmaz', async () => {
    const u = await mkUser(); const t = await issueToken(u.id, 'reset');
    const row = await prisma.authToken.findFirst({ where: { userId: u.id } });
    expect(row!.tokenHash).not.toContain(t); expect(row!.tokenHash).toHaveLength(64);
  });
  it('süresi dolan jeton reddedilir', async () => {
    const u = await mkUser(); const t = await issueToken(u.id, 'reset');
    await prisma.authToken.updateMany({ where: { userId: u.id }, data: { expiresAt: new Date(Date.now() - 1000) } });
    expect(await consumeToken(t, 'reset')).toBeNull();
  });
  it('yeni jeton eskisini geçersiz kılar', async () => {
    const u = await mkUser(); const a = await issueToken(u.id, 'reset'); const b = await issueToken(u.id, 'reset');
    expect(await consumeToken(a, 'reset')).toBeNull(); expect(await consumeToken(b, 'reset')).toBe(u.id);
  });
  it('şablonlar Türkçe ve bağlantıyı içeriyor', () => {
    expect(verifyEmail('Deniz Aksoy', 'https://x/dogrula?token=abc').html).toContain('Merhaba Deniz');
    expect(resetEmail('Deniz', 'https://x/s?token=abc').html).toContain('1 saat');
  });
});

describe('şifre sıfırlama akışı', () => {
  it('kayıtsız adres için aynı yanıt, e-posta gönderilmez', async () => {
    const r = await requestReset(undefined, fd({ email: 'yok@test.tr' }));
    expect(r.ok).toBeTruthy(); expect(sent).toHaveLength(0);
  });
  it('talep → e-posta → yeni şifre → eski oturumlar düşer', async () => {
    const u = await prisma.user.create({ data: { email: 'a@test.tr', name: 'Ayşe Kaya', passwordHash: await hashPassword('eski-sifre-1'), sessionVersion: 0 } });
    const r = await requestReset(undefined, fd({ email: 'A@test.tr' }));
    expect(r.ok).toBeTruthy(); expect(sent[0].to).toBe('a@test.tr');
    const token = tokenFrom(sent[0].html);
    expect((await resetPassword(undefined, fd({ token, password: 'yeni-sifre-2', password2: 'farkli-sifre' })))?.error).toMatch(/aynı değil/);
    await expect(resetPassword(undefined, fd({ token, password: 'yeni-sifre-2', password2: 'yeni-sifre-2' }))).rejects.toMatchObject({ url: '/panel?sifre=degisti' });
    const u2 = await prisma.user.findUnique({ where: { id: u.id } });
    expect(await verifyPassword('yeni-sifre-2', u2!.passwordHash)).toBe(true);
    expect(u2!.sessionVersion).toBe(1); expect(u2!.emailVerifiedAt).not.toBeNull();
    // aynı jeton ikinci kez kullanılamaz
    expect((await resetPassword(undefined, fd({ token, password: 'ucuncu-sifre', password2: 'ucuncu-sifre' })))?.error).toMatch(/süresi dolmuş/);
  });
  it('aynı e-posta için saatte 3 talepten fazlası e-posta göndermez', async () => {
    await prisma.user.create({ data: { email: 'b@test.tr', name: 'B', passwordHash: 'x' } });
    for (let i = 0; i < 5; i++) await requestReset(undefined, fd({ email: 'b@test.tr' }));
    expect(sent).toHaveLength(3);
  });
});
