import { test } from '@japa/runner'
import testUtils from '@adonisjs/core/services/test_utils'
import User from '#models/user'
import Plan from '#models/plan'
import { createTestUser } from '../helpers/auth.js'

/**
 * Tests d'auth : signup, login, refresh, logout, perms.
 *
 * `withGlobalTransaction` wrap chaque test dans une tx PG qui est
 * rollback à la fin → pas besoin de truncate, isolation parfaite.
 */
test.group('Auth — POST /auth/signup', (group) => {
  group.each.setup(() => testUtils.db().withGlobalTransaction())

  test('crée user + org + 4 plans pré-fournis dans une tx', async ({ client, assert }) => {
    const response = await client.post('/api/v1/auth/signup').json({
      email: 'alice@spec.test',
      password: 'password123',
      fullName: 'Alice Spec',
    })

    response.assertStatus(201)
    const data = response.body().data
    assert.properties(data, ['accessToken', 'expiresAt', 'user'])
    assert.properties(data.user, ['id', 'email', 'organizationId', 'fullName'])
    assert.equal(data.user.email, 'alice@spec.test')

    // L'org est créée mais le nom reste vide (rempli en onboarding).
    const user = await User.findByOrFail('email', 'alice@spec.test')
    assert.isNotNull(user.organizationId)

    // Les 4 plans pré-fournis sont en place pour cette org.
    const plans = await Plan.query().where('organization_id', user.organizationId!)
    assert.lengthOf(plans, 4)
    const slugs = plans.map((p) => p.slug).sort()
    assert.deepEqual(slugs, ['ferme-7j', 'patient-60j', 'rapide-15j', 'standard-30j'])
  })

  test('refuse un email mal formé (422)', async ({ client }) => {
    const response = await client.post('/api/v1/auth/signup').json({
      email: 'pas-un-email',
      password: 'password123',
      fullName: 'Test',
    })
    response.assertStatus(422)
  })

  test('refuse un password < 8 chars (422)', async ({ client }) => {
    const response = await client.post('/api/v1/auth/signup').json({
      email: 'short@spec.test',
      password: 'abc',
      fullName: 'Test',
    })
    response.assertStatus(422)
  })

  test('refuse un email déjà pris (422)', async ({ client }) => {
    await client.post('/api/v1/auth/signup').json({
      email: 'twice@spec.test',
      password: 'password123',
      fullName: 'First',
    })

    const response = await client.post('/api/v1/auth/signup').json({
      email: 'twice@spec.test',
      password: 'password123',
      fullName: 'Second',
    })
    response.assertStatus(422)
  })
})

test.group('Auth — POST /auth/login', (group) => {
  group.each.setup(() => testUtils.db().withGlobalTransaction())

  test('émet une AuthSession avec credentials valides', async ({ client, assert }) => {
    const { user, plainPassword } = await createTestUser()

    const response = await client.post('/api/v1/auth/login').json({
      email: user.email,
      password: plainPassword,
    })

    response.assertStatus(200)
    const data = response.body().data
    assert.equal(data.user.email, user.email)
    assert.isString(data.accessToken)
  })

  test('rejette un mauvais password (401)', async ({ client }) => {
    const { user } = await createTestUser()
    const response = await client.post('/api/v1/auth/login').json({
      email: user.email,
      password: 'wrong-password',
    })
    response.assertStatus(401)
  })

  test('rejette un email inconnu (401)', async ({ client }) => {
    const response = await client.post('/api/v1/auth/login').json({
      email: 'ghost@spec.test',
      password: 'password123',
    })
    response.assertStatus(401)
  })
})

test.group('Auth — bearer requis', (group) => {
  group.each.setup(() => testUtils.db().withGlobalTransaction())

  test('GET /account/profile → 401 sans token', async ({ client }) => {
    const response = await client.get('/api/v1/account/profile')
    response.assertStatus(401)
  })

  test('GET /account/profile → 401 avec un token bidon', async ({ client }) => {
    const response = await client
      .get('/api/v1/account/profile')
      .header('Authorization', 'Bearer invalid-token')
    response.assertStatus(401)
  })

  test('GET /account/profile → 200 avec token valide', async ({ client, assert }) => {
    const { user, accessToken } = await createTestUser()
    const response = await client
      .get('/api/v1/account/profile')
      .header('Authorization', `Bearer ${accessToken}`)

    response.assertStatus(200)
    assert.equal(response.body().data.email, user.email)
  })
})

test.group('Auth — POST /account/logout', (group) => {
  group.each.setup(() => testUtils.db().withGlobalTransaction())

  test('révoque le token courant (204)', async ({ client }) => {
    const { accessToken } = await createTestUser()

    const logout = await client
      .post('/api/v1/account/logout')
      .header('Authorization', `Bearer ${accessToken}`)
    logout.assertStatus(204)

    // Le token doit être révoqué : la requête suivante revient 401.
    const after = await client
      .get('/api/v1/account/profile')
      .header('Authorization', `Bearer ${accessToken}`)
    after.assertStatus(401)
  })
})

test.group('Auth — onboarding state', (group) => {
  group.each.setup(() => testUtils.db().withGlobalTransaction())

  test('PATCH /organizations/me pose onboardingCompletedAt à la 1re mise du nom', async ({
    client,
    assert,
  }) => {
    const { accessToken } = await createTestUser()

    // 1er PATCH : pose onboardingCompletedAt
    const r1 = await client
      .patch('/api/v1/organizations/me')
      .header('Authorization', `Bearer ${accessToken}`)
      .json({ name: 'Boulangerie Spec' })
    r1.assertStatus(200)
    assert.isNotNull(r1.body().data.onboardingCompletedAt)

    // 2e PATCH : ne touche pas onboardingCompletedAt (déjà set)
    const onboardedAt = r1.body().data.onboardingCompletedAt
    const r2 = await client
      .patch('/api/v1/organizations/me')
      .header('Authorization', `Bearer ${accessToken}`)
      .json({ name: 'Boulangerie Spec 2' })
    r2.assertStatus(200)
    assert.equal(r2.body().data.onboardingCompletedAt, onboardedAt)
  })
})
