import { afterEach, describe, it, expect, beforeEach } from 'vitest';
import { mkdtempSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { Repository } from './repository.js';
import { runMigrations } from './migrate.js';

describe('Repository user CRUD', () => {
  let tempDir = '';
  let repo: Repository;

  beforeEach(() => {
    tempDir = mkdtempSync(join(tmpdir(), 'maestro-auth-'));
    repo = new Repository(join(tempDir, 'orchestrator.db'));
    runMigrations(repo.getDb());
  });

  afterEach(() => {
    repo.close();
    if (tempDir) {
      rmSync(tempDir, { recursive: true, force: true });
      tempDir = '';
    }
  });

  // ── createUser ────────────────────────────────────────────────

  it('createUser creates a user with correct fields', () => {
    const user = repo.createUser({
      email: 'alice@example.com',
      name: 'Alice',
      role: 'user',
      status: 'pending',
    });

    expect(user.id).toBeTruthy();
    expect(user.email).toBe('alice@example.com');
    expect(user.name).toBe('Alice');
    expect(user.role).toBe('user');
    expect(user.status).toBe('pending');
    expect(user.avatarUrl).toBeNull();
    expect(user.createdAt).toBeTruthy();
    expect(user.updatedAt).toBeTruthy();
  });

  it('createUser stores avatarUrl when provided', () => {
    const user = repo.createUser({
      email: 'bob@example.com',
      name: 'Bob',
      role: 'admin',
      status: 'active',
      avatarUrl: 'https://example.com/avatar.png',
    });

    expect(user.avatarUrl).toBe('https://example.com/avatar.png');
    expect(user.role).toBe('admin');
    expect(user.status).toBe('active');
  });

  // ── getUserByEmail ────────────────────────────────────────────

  it('getUserByEmail returns null for non-existent email', () => {
    const user = repo.getUserByEmail('nonexistent@example.com');
    expect(user).toBeNull();
  });

  it('getUserByEmail returns the user for known email', () => {
    repo.createUser({ email: 'carol@example.com', name: 'Carol', role: 'user', status: 'active' });
    const user = repo.getUserByEmail('carol@example.com');
    expect(user).not.toBeNull();
    expect(user?.email).toBe('carol@example.com');
    expect(user?.name).toBe('Carol');
  });

  // ── getUserById ───────────────────────────────────────────────

  it('getUserById returns the user for known id', () => {
    const created = repo.createUser({ email: 'dave@example.com', name: 'Dave', role: 'user', status: 'active' });
    const found = repo.getUserById(created.id);
    expect(found).not.toBeNull();
    expect(found?.id).toBe(created.id);
  });

  it('getUserById returns null for unknown id', () => {
    const found = repo.getUserById('00000000-0000-0000-0000-000000000000');
    expect(found).toBeNull();
  });

  // ── findOrCreateUserByOAuth ───────────────────────────────────

  it('findOrCreateUserByOAuth creates new user on first login with status=pending', () => {
    const user = repo.findOrCreateUserByOAuth({
      provider: 'discord',
      providerId: 'discord-uid-1',
      email: 'eve@example.com',
      name: 'Eve',
    });

    expect(user.email).toBe('eve@example.com');
    expect(user.name).toBe('Eve');
    expect(user.status).toBe('pending');
  });

  it('findOrCreateUserByOAuth returns same user on subsequent login (same provider_id)', () => {
    const params = {
      provider: 'discord',
      providerId: 'discord-uid-2',
      email: 'frank@example.com',
      name: 'Frank',
    };

    const first = repo.findOrCreateUserByOAuth(params);
    const second = repo.findOrCreateUserByOAuth(params);

    expect(second.id).toBe(first.id);
    expect(second.email).toBe('frank@example.com');
  });

  it('findOrCreateUserByOAuth links second provider to same user by matching email', () => {
    // First login via discord
    const discordUser = repo.findOrCreateUserByOAuth({
      provider: 'discord',
      providerId: 'discord-uid-3',
      email: 'grace@example.com',
      name: 'Grace',
    });

    // Second login via github with same email
    const githubUser = repo.findOrCreateUserByOAuth({
      provider: 'github',
      providerId: 'github-uid-3',
      email: 'grace@example.com',
      name: 'Grace GitHub',
    });

    // Should return same user
    expect(githubUser.id).toBe(discordUser.id);

    // Both providers should now be linked to this user
    const userAgain = repo.findOrCreateUserByOAuth({
      provider: 'discord',
      providerId: 'discord-uid-3',
      email: 'grace@example.com',
      name: 'Grace',
    });
    expect(userAgain.id).toBe(discordUser.id);
  });

  // ── listUsers ─────────────────────────────────────────────────

  it('listUsers returns all users', () => {
    repo.createUser({ email: 'u1@example.com', name: 'U1', role: 'user', status: 'active' });
    repo.createUser({ email: 'u2@example.com', name: 'U2', role: 'user', status: 'pending' });
    repo.createUser({ email: 'u3@example.com', name: 'U3', role: 'admin', status: 'active' });

    const users = repo.listUsers();
    expect(users.length).toBe(3);
    const emails = users.map(u => u.email);
    expect(emails).toContain('u1@example.com');
    expect(emails).toContain('u2@example.com');
    expect(emails).toContain('u3@example.com');
  });

  // ── updateUser ────────────────────────────────────────────────

  it('updateUser changes status and role', () => {
    const user = repo.createUser({ email: 'henry@example.com', name: 'Henry', role: 'user', status: 'pending' });

    repo.updateUser(user.id, { status: 'active', role: 'admin' });

    const updated = repo.getUserById(user.id);
    expect(updated?.status).toBe('active');
    expect(updated?.role).toBe('admin');
  });

  // ── deleteUser ────────────────────────────────────────────────

  it('deleteUser removes user from DB', () => {
    const user = repo.createUser({ email: 'iris@example.com', name: 'Iris', role: 'user', status: 'active' });
    repo.deleteUser(user.id);
    const found = repo.getUserById(user.id);
    expect(found).toBeNull();
  });

  it('deleteUser cascades to oauth_accounts', () => {
    const user = repo.findOrCreateUserByOAuth({
      provider: 'discord',
      providerId: 'discord-uid-cascade',
      email: 'jack@example.com',
      name: 'Jack',
    });

    repo.deleteUser(user.id);

    // OAuth account should be gone too (CASCADE)
    // Verify by trying to re-create the same OAuth -> should create fresh user
    const newUser = repo.findOrCreateUserByOAuth({
      provider: 'discord',
      providerId: 'discord-uid-cascade',
      email: 'jack@example.com',
      name: 'Jack',
    });
    expect(newUser.id).not.toBe(user.id);
  });
});

// ── owner_id filtering ────────────────────────────────────────────

describe('Repository owner_id filtering', () => {
  let tempDir = '';
  let repo: Repository;

  beforeEach(() => {
    tempDir = mkdtempSync(join(tmpdir(), 'maestro-auth-'));
    repo = new Repository(join(tempDir, 'orchestrator.db'));
    runMigrations(repo.getDb());
  });

  afterEach(() => {
    repo.close();
    if (tempDir) {
      rmSync(tempDir, { recursive: true, force: true });
      tempDir = '';
    }
  });

  it('createLocalTask accepts ownerId parameter', async () => {
    const user = repo.createUser({ email: 'owner@example.com', name: 'Owner', role: 'user', status: 'active' });

    const task = await repo.createLocalTask({
      title: 'Test Task',
      body: 'body',
      ownerId: user.id,
    });

    expect(task.id).toBeTruthy();

    // Verify owner_id was stored in DB
    const db = repo.getDb();
    const row = db.prepare('SELECT owner_id FROM local_tasks WHERE id = ?').get(task.id) as { owner_id: string | null } | undefined;
    expect(row?.owner_id).toBe(user.id);
  });

  it('listLocalTasks filters by ownerId', async () => {
    const user1 = repo.createUser({ email: 'owner1@example.com', name: 'Owner1', role: 'user', status: 'active' });
    const user2 = repo.createUser({ email: 'owner2@example.com', name: 'Owner2', role: 'user', status: 'active' });

    await repo.createLocalTask({ title: 'Task A', body: '', ownerId: user1.id });
    await repo.createLocalTask({ title: 'Task B', body: '', ownerId: user1.id });
    await repo.createLocalTask({ title: 'Task C', body: '', ownerId: user2.id });
    await repo.createLocalTask({ title: 'Task D', body: '' }); // no owner

    const user1Tasks = await repo.listLocalTasks({ ownerId: user1.id });
    expect(user1Tasks.length).toBe(2);
    const titles1 = user1Tasks.map(t => t.title);
    expect(titles1).toContain('Task A');
    expect(titles1).toContain('Task B');

    const user2Tasks = await repo.listLocalTasks({ ownerId: user2.id });
    expect(user2Tasks.length).toBe(1);
    expect(user2Tasks[0].title).toBe('Task C');

    // Without filter returns all tasks
    const allTasks = await repo.listLocalTasks();
    expect(allTasks.length).toBe(4);
  });
});
