const { describe, test, expect, beforeEach, afterEach } = require('bun:test')
const crypto = require('crypto')
const path = require('path')
const fs = require('fs')
const Database = require('better-sqlite3')
const { initEncryption } = require('./encryption')

const testDbPath = path.join(__dirname, '.test-users.db')
const testMasterKey = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'

let testDb
let userRepository

function createTestSchema(db) {
  db.exec(`
    CREATE TABLE IF NOT EXISTS users (
      id TEXT PRIMARY KEY,
      email TEXT NOT NULL UNIQUE,
      email_encrypted TEXT,
      name TEXT,
      name_encrypted TEXT,
      password_hash TEXT NOT NULL,
      providers TEXT DEFAULT '[]',
      email_verified INTEGER DEFAULT 0,
      verification_token TEXT,
      verification_expires_at INTEGER,
      reset_token TEXT,
      reset_expires_at INTEGER,
      plan TEXT DEFAULT 'hobby',
      billing_status TEXT DEFAULT 'active',
      billing_email TEXT,
      payment_method_last4 TEXT,
      subscription_renews_at INTEGER,
      referred_by_affiliate_code TEXT,
      affiliate_attribution_at INTEGER,
      affiliate_payouts TEXT DEFAULT '[]',
      two_factor_secret TEXT,
      two_factor_enabled INTEGER DEFAULT 0,
      created_at INTEGER NOT NULL,
      updated_at INTEGER NOT NULL,
      last_login_at INTEGER
    )
  `)
}

describe('User Repository', () => {
  beforeEach(async () => {
    initEncryption(testMasterKey)
    
    if (fs.existsSync(testDbPath)) {
      fs.unlinkSync(testDbPath)
    }
    
    testDb = new Database(testDbPath)
    createTestSchema(testDb)
    
    const { getDatabase } = require('../database/connection')
    const originalGetDatabase = require('../database/connection').getDatabase
    
    const mockConnection = {
      getDatabase: () => testDb
    }
    
    userRepository = require('./userRepository')
    
    const originalModule = require.cache[require.resolve('./userRepository')]
    if (originalModule) {
      delete require.cache[require.resolve('./userRepository')]
    }
    
    process.env.TEST_DB = testDbPath
  })

  afterEach(() => {
    if (testDb) {
      testDb.close()
    }
    if (fs.existsSync(testDbPath)) {
      fs.unlinkSync(testDbPath)
    }
  })

  describe('User CRUD Operations', () => {
    test('creates a user with required fields', () => {
      const userData = {
        id: 'user-test-1',
        email: 'test@example.com',
        passwordHash: '$2b$12$hashedpassword123'
      }
      
      const stmt = testDb.prepare(`
        INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
      `)
      
      const now = Date.now()
      stmt.run(
        userData.id,
        userData.email,
        'encrypted-email',
        userData.passwordHash,
        'hobby',
        'active',
        '[]',
        now,
        now
      )
      
      const user = testDb.prepare('SELECT * FROM users WHERE id = ?').get(userData.id)
      
      expect(user).toBeDefined()
      expect(user.email).toBe(userData.email)
      expect(user.plan).toBe('hobby')
      expect(user.billing_status).toBe('active')
    })

    test('creates user with all optional fields', () => {
      const now = Date.now()
      const userData = {
        id: 'user-test-2',
        email: 'full@example.com',
        name: 'Test User',
        passwordHash: '$2b$12$hashedpassword123',
        emailVerified: true,
        plan: 'professional',
        billingStatus: 'active',
        billingEmail: 'billing@example.com'
      }
      
      const stmt = testDb.prepare(`
        INSERT INTO users (
          id, email, email_encrypted, name, name_encrypted, password_hash,
          email_verified, plan, billing_status, billing_email, providers, created_at, updated_at
        )
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      `)
      
      stmt.run(
        userData.id,
        userData.email,
        'encrypted-email',
        userData.name,
        'encrypted-name',
        userData.passwordHash,
        userData.emailVerified ? 1 : 0,
        userData.plan,
        userData.billingStatus,
        userData.billingEmail,
        '[]',
        now,
        now
      )
      
      const user = testDb.prepare('SELECT * FROM users WHERE id = ?').get(userData.id)
      
      expect(user.name).toBe(userData.name)
      expect(user.email_verified).toBe(1)
      expect(user.plan).toBe('professional')
      expect(user.billing_email).toBe(userData.billingEmail)
    })

    test('gets user by ID', () => {
      const now = Date.now()
      testDb.prepare(`
        INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run('user-get-1', 'get@example.com', 'encrypted', 'hash', 'hobby', 'active', '[]', now, now)
      
      const user = testDb.prepare('SELECT * FROM users WHERE id = ?').get('user-get-1')
      
      expect(user).toBeDefined()
      expect(user.id).toBe('user-get-1')
      expect(user.email).toBe('get@example.com')
    })

    test('returns null for non-existent user', () => {
      const user = testDb.prepare('SELECT * FROM users WHERE id = ?').get('non-existent')
      expect(user).toBeUndefined()
    })

    test('gets user by email', () => {
      const now = Date.now()
      testDb.prepare(`
        INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run('user-email-1', 'email@example.com', 'encrypted', 'hash', 'hobby', 'active', '[]', now, now)
      
      const user = testDb.prepare('SELECT * FROM users WHERE email = ?').get('email@example.com')
      
      expect(user).toBeDefined()
      expect(user.id).toBe('user-email-1')
    })

    test('updates user plan', () => {
      const now = Date.now()
      testDb.prepare(`
        INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run('user-update-1', 'update@example.com', 'encrypted', 'hash', 'hobby', 'active', '[]', now, now)
      
      testDb.prepare('UPDATE users SET plan = ?, updated_at = ? WHERE id = ?')
        .run('professional', Date.now(), 'user-update-1')
      
      const user = testDb.prepare('SELECT * FROM users WHERE id = ?').get('user-update-1')
      
      expect(user.plan).toBe('professional')
    })

    test('updates user email verification status', () => {
      const now = Date.now()
      testDb.prepare(`
        INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run('user-verify-1', 'verify@example.com', 'encrypted', 'hash', 'hobby', 'active', '[]', now, now)
      
      testDb.prepare('UPDATE users SET email_verified = ?, updated_at = ? WHERE id = ?')
        .run(1, Date.now(), 'user-verify-1')
      
      const user = testDb.prepare('SELECT * FROM users WHERE id = ?').get('user-verify-1')
      
      expect(user.email_verified).toBe(1)
    })

    test('deletes user', () => {
      const now = Date.now()
      testDb.prepare(`
        INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run('user-delete-1', 'delete@example.com', 'encrypted', 'hash', 'hobby', 'active', '[]', now, now)
      
      const result = testDb.prepare('DELETE FROM users WHERE id = ?').run('user-delete-1')
      
      expect(result.changes).toBe(1)
      
      const user = testDb.prepare('SELECT * FROM users WHERE id = ?').get('user-delete-1')
      expect(user).toBeUndefined()
    })

    test('gets all users with pagination', () => {
      const now = Date.now()
      for (let i = 0; i < 15; i++) {
        testDb.prepare(`
          INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at)
          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
        `).run(`user-page-${i}`, `page${i}@example.com`, 'encrypted', 'hash', 'hobby', 'active', '[]', now + i, now + i)
      }
      
      const page1 = testDb.prepare('SELECT * FROM users ORDER BY created_at DESC LIMIT 10 OFFSET 0').all()
      const page2 = testDb.prepare('SELECT * FROM users ORDER BY created_at DESC LIMIT 10 OFFSET 10').all()
      
      expect(page1.length).toBe(10)
      expect(page2.length).toBe(5)
    })

    test('counts total users', () => {
      const now = Date.now()
      for (let i = 0; i < 5; i++) {
        testDb.prepare(`
          INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at)
          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
        `).run(`user-count-${i}`, `count${i}@example.com`, 'encrypted', 'hash', 'hobby', 'active', '[]', now, now)
      }
      
      const result = testDb.prepare('SELECT COUNT(*) as count FROM users').get()
      
      expect(result.count).toBe(5)
    })
  })

  describe('User Authentication Fields', () => {
    test('sets verification token', () => {
      const now = Date.now()
      const token = crypto.randomUUID()
      const expiresAt = now + (24 * 60 * 60 * 1000)
      
      testDb.prepare(`
        INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run('user-token-1', 'token@example.com', 'encrypted', 'hash', 'hobby', 'active', '[]', now, now)
      
      testDb.prepare('UPDATE users SET verification_token = ?, verification_expires_at = ? WHERE id = ?')
        .run(token, expiresAt, 'user-token-1')
      
      const user = testDb.prepare('SELECT * FROM users WHERE verification_token = ?').get(token)
      
      expect(user).toBeDefined()
      expect(user.verification_token).toBe(token)
    })

    test('sets password reset token', () => {
      const now = Date.now()
      const token = crypto.randomUUID()
      const expiresAt = now + (60 * 60 * 1000)
      
      testDb.prepare(`
        INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run('user-reset-1', 'reset@example.com', 'encrypted', 'hash', 'hobby', 'active', '[]', now, now)
      
      testDb.prepare('UPDATE users SET reset_token = ?, reset_expires_at = ? WHERE id = ?')
        .run(token, expiresAt, 'user-reset-1')
      
      const user = testDb.prepare('SELECT * FROM users WHERE reset_token = ?').get(token)
      
      expect(user).toBeDefined()
      expect(user.reset_token).toBe(token)
    })

    test('clears verification token after verification', () => {
      const now = Date.now()
      const token = crypto.randomUUID()
      
      testDb.prepare(`
        INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at, verification_token, verification_expires_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run('user-clear-1', 'clear@example.com', 'encrypted', 'hash', 'hobby', 'active', '[]', now, now, token, now + 86400000)
      
      testDb.prepare('UPDATE users SET verification_token = NULL, verification_expires_at = NULL, email_verified = 1 WHERE id = ?')
        .run('user-clear-1')
      
      const user = testDb.prepare('SELECT * FROM users WHERE id = ?').get('user-clear-1')
      
      expect(user.verification_token).toBeNull()
      expect(user.email_verified).toBe(1)
    })
  })

  describe('User Plan Management', () => {
    test('enforces valid plan values', () => {
      const validPlans = ['hobby', 'starter', 'professional', 'enterprise']
      
      validPlans.forEach(plan => {
        expect(['hobby', 'starter', 'professional', 'enterprise'].includes(plan)).toBe(true)
      })
    })

    test('updates billing status', () => {
      const now = Date.now()
      testDb.prepare(`
        INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run('user-billing-1', 'billing@example.com', 'encrypted', 'hash', 'professional', 'active', '[]', now, now)
      
      testDb.prepare('UPDATE users SET billing_status = ? WHERE id = ?')
        .run('past_due', 'user-billing-1')
      
      const user = testDb.prepare('SELECT * FROM users WHERE id = ?').get('user-billing-1')
      
      expect(user.billing_status).toBe('past_due')
    })

    test('sets subscription renewal date', () => {
      const now = Date.now()
      const renewsAt = now + (30 * 24 * 60 * 60 * 1000)
      
      testDb.prepare(`
        INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run('user-renew-1', 'renew@example.com', 'encrypted', 'hash', 'professional', 'active', '[]', now, now)
      
      testDb.prepare('UPDATE users SET subscription_renews_at = ? WHERE id = ?')
        .run(renewsAt, 'user-renew-1')
      
      const user = testDb.prepare('SELECT * FROM users WHERE id = ?').get('user-renew-1')
      
      expect(user.subscription_renews_at).toBe(renewsAt)
    })
  })

  describe('User OAuth Providers', () => {
    test('stores multiple providers', () => {
      const now = Date.now()
      const providers = ['google', 'github']
      
      testDb.prepare(`
        INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run('user-oauth-1', 'oauth@example.com', 'encrypted', 'hash', 'hobby', 'active', JSON.stringify(providers), now, now)
      
      const user = testDb.prepare('SELECT * FROM users WHERE id = ?').get('user-oauth-1')
      const parsedProviders = JSON.parse(user.providers)
      
      expect(parsedProviders).toContain('google')
      expect(parsedProviders).toContain('github')
    })

    test('adds provider to existing user', () => {
      const now = Date.now()
      testDb.prepare(`
        INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run('user-add-oauth-1', 'add-oauth@example.com', 'encrypted', 'hash', 'hobby', 'active', '["google"]', now, now)
      
      const user = testDb.prepare('SELECT * FROM users WHERE id = ?').get('user-add-oauth-1')
      const providers = JSON.parse(user.providers)
      providers.push('github')
      
      testDb.prepare('UPDATE users SET providers = ? WHERE id = ?')
        .run(JSON.stringify(providers), 'user-add-oauth-1')
      
      const updated = testDb.prepare('SELECT * FROM users WHERE id = ?').get('user-add-oauth-1')
      expect(JSON.parse(updated.providers)).toContain('github')
    })
  })

  describe('Two-Factor Authentication', () => {
    test('enables 2FA', () => {
      const now = Date.now()
      testDb.prepare(`
        INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run('user-2fa-1', '2fa@example.com', 'encrypted', 'hash', 'hobby', 'active', '[]', now, now)
      
      testDb.prepare('UPDATE users SET two_factor_enabled = 1, two_factor_secret = ? WHERE id = ?')
        .run('encrypted-secret', 'user-2fa-1')
      
      const user = testDb.prepare('SELECT * FROM users WHERE id = ?').get('user-2fa-1')
      
      expect(user.two_factor_enabled).toBe(1)
      expect(user.two_factor_secret).toBe('encrypted-secret')
    })

    test('disables 2FA', () => {
      const now = Date.now()
      testDb.prepare(`
        INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at, two_factor_enabled, two_factor_secret)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run('user-2fa-disable-1', '2fa-disable@example.com', 'encrypted', 'hash', 'hobby', 'active', '[]', now, now, 1, 'encrypted-secret')
      
      testDb.prepare('UPDATE users SET two_factor_enabled = 0, two_factor_secret = NULL WHERE id = ?')
        .run('user-2fa-disable-1')
      
      const user = testDb.prepare('SELECT * FROM users WHERE id = ?').get('user-2fa-disable-1')
      
      expect(user.two_factor_enabled).toBe(0)
      expect(user.two_factor_secret).toBeNull()
    })
  })

  describe('Affiliate Attribution', () => {
    test('sets affiliate referral code', () => {
      const now = Date.now()
      testDb.prepare(`
        INSERT INTO users (id, email, email_encrypted, password_hash, plan, billing_status, providers, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run('user-aff-1', 'aff@example.com', 'encrypted', 'hash', 'hobby', 'active', '[]', now, now)
      
      testDb.prepare('UPDATE users SET referred_by_affiliate_code = ?, affiliate_attribution_at = ? WHERE id = ?')
        .run('AFFILIATE123', now, 'user-aff-1')
      
      const user = testDb.prepare('SELECT * FROM users WHERE id = ?').get('user-aff-1')
      
      expect(user.referred_by_affiliate_code).toBe('AFFILIATE123')
      expect(user.affiliate_attribution_at).toBe(now)
    })
  })
})
