import { test, expect, request } from '@playwright/test';
import { v4 as uuidv4 } from 'uuid';
import Database from '../../server/node_modules/better-sqlite3/lib/index.js';
import path from 'path';

const API = 'http://localhost:3001/api';

interface Ctx {
  token: string;
  get: (path: string, opts?: any) => Promise<any>;
  post: (path: string, opts?: any) => Promise<any>;
  put: (path: string, opts?: any) => Promise<any>;
  delete: (path: string, opts?: any) => Promise<any>;
  dispose: () => Promise<void>;
}

async function login(email: string, password: string): Promise<Ctx> {
  const ctx = await request.newContext();
  const res = await ctx.post(`${API}/auth/login`, { data: { email, password } });
  expect(res.ok(), `login failed for ${email}`).toBeTruthy();
  const body = await res.json();
  const token = body.token as string;
  const auth = { Authorization: `Bearer ${token}` };
  return {
    token,
    get: (path, opts = {}) => ctx.get(`${API}${path}`, { ...opts, headers: { ...(opts.headers || {}), ...auth } }),
    post: (path, opts = {}) => ctx.post(`${API}${path}`, { ...opts, headers: { ...(opts.headers || {}), ...auth } }),
    put: (path, opts = {}) => ctx.put(`${API}${path}`, { ...opts, headers: { ...(opts.headers || {}), ...auth } }),
    delete: (path, opts = {}) => ctx.delete(`${API}${path}`, { ...opts, headers: { ...(opts.headers || {}), ...auth } }),
    dispose: () => ctx.dispose(),
  };
}

test.describe('Offboarding Workflows (Phase 3)', () => {
  let admin: Ctx;

  test.beforeAll(async () => {
    admin = await login('admin@school.com', 'admin123');
  });

  test.afterAll(async () => {
    await admin.dispose();
  });

  // Helper to create a user via raw SQL / API or we can just use the create user endpoint if it exists
  // In the admin dashboard, we can register an account by posting to /api/users
  const createAccount = async (role: string, email: string) => {
    const res = await admin.post('/users', {
      data: {
        email,
        password: 'password123',
        first_name: 'E2E',
        last_name: 'TestUser',
        role,
        phone: '12345678',
      },
    });
    expect(res.ok(), `Failed to create user: ${await res.text()}`).toBeTruthy();
    const body = await res.json();
    // In our users controller, it returns the created user object which contains the ID
    return body.user || body;
  };

  test('1. student offboarding happy path (no fees)', async () => {
    const email = `stud-${uuidv4()}@school.com`;
    const student = await createAccount('student', email);

    // 1. Preflight checks
    const preRes = await admin.get(`/offboarding/preflight/${student.id}`);
    expect(preRes.ok()).toBeTruthy();
    const preBody = await preRes.json();
    expect(preBody.role).toBe('student');
    expect(preBody.checks.fees).toBe(0);

    // 2. Run offboarding
    const offRes = await admin.post(`/offboarding/students/${student.id}`, {
      data: {
        reason: 'graduated',
        effectiveDate: '2026-07-22',
        reasonNotes: 'E2E graduation',
      },
    });
    expect(offRes.ok(), `Offboarding student failed: ${await offRes.text()}`).toBeTruthy();
    const offBody = await offRes.json();
    expect(offBody.record.status).toBe('completed');
    expect(offBody.record.reason).toBe('graduated');
    expect(offBody.record.alumni_status).toBe('active_alumni');
    expect(offBody.actions.length).toBe(11);

    // Check action states are all completed
    offBody.actions.forEach((a: any) => {
      expect(a.status).toBe('completed');
    });

    // 3. Confirm in alumni directory
    const alumniRes = await admin.get(`/offboarding/alumni?search=${email}`);
    expect(alumniRes.ok()).toBeTruthy();
    const alumniList = await alumniRes.json();
    expect(alumniList.length).toBe(1);
    expect(alumniList[0].id).toBe(student.id);
  });

  test('2. student offboarding blocked by fees and bypassed via override', async () => {
    const email = `stud-${uuidv4()}@school.com`;
    const student = await createAccount('student', email);

    // To add a fee, let's look at the database. In local SQLite, we can insert directly in the E2E test?
    // E2E test runs outside database context, so it interacts via HTTP.
    // Is there a way to assign a fee group or generate invoices?
    // Let's check: we can create a fee plan or write student fee.
    // Wait, let's see if there is an endpoint to create a student fee.
    // Typically, bursar or admin can run fees invoice creation.
    // Let's check: in `fees.controller.js`, there is `POST /api/fees` or similar?
    // Or maybe we can just query the database in the test using `better-sqlite3`?
    // Yes! In Playwright, since we run locally, we can import `better-sqlite3` and insert a record directly into `student_fees`!
    // This is super fast, deterministic, and doesn't rely on fee UI endpoints which might change.
    // Let's do that!
    const dbPath = process.env.DB_PATH || path.resolve('../server/data/school.db');
    const db = new Database(dbPath);
    db.pragma('foreign_keys = ON');

    // Create a fee group and student fee
    const feeGroupUid = uuidv4();
    const groupResult = db.prepare("INSERT INTO fee_groups (uid, name, amount) VALUES (?, 'E2E Tuition', 150)").run(feeGroupUid);
    db.prepare(`
      INSERT INTO student_fees (uid, student_id, fee_group_id, amount, paid_amount, status)
      VALUES (?, ?, ?, 150, 0, 'pending')
    `).run(uuidv4(), student.id, groupResult.lastInsertRowid);
    db.close();

    // 1. Verify preflight shows outstanding fees
    const preRes = await admin.get(`/offboarding/preflight/${student.id}`);
    expect(preRes.ok()).toBeTruthy();
    const preBody = await preRes.json();
    expect(preBody.checks.fees).toBe(150);

    // 2. Run offboarding without override -> expect 400 Bad Request
    const failRes = await admin.post(`/offboarding/students/${student.id}`, {
      data: {
        reason: 'withdrawn',
        effectiveDate: '2026-07-22',
      },
    });
    expect(failRes.status()).toBe(400);
    const failBody = await failRes.json();
    expect(failBody.error).toContain('fees');

    // 3. Run offboarding WITH override -> expect success (201)
    const successRes = await admin.post(`/offboarding/students/${student.id}`, {
      data: {
        reason: 'withdrawn',
        effectiveDate: '2026-07-22',
        override: {
          reason: 'Bypassed by principal approval',
        },
      },
    });
    expect(successRes.ok(), `Override failed: ${await successRes.text()}`).toBeTruthy();
    const successBody = await successRes.json();
    expect(successBody.record.status).toBe('completed');
    
    const feeAction = successBody.actions.find((a: any) => a.step === 'fee_settled');
    expect(feeAction.status).toBe('completed');
    expect(feeAction.notes).toContain('Bypassed by principal approval');
  });

  test('3. staff offboarding happy path', async () => {
    const email = `teach-${uuidv4()}@school.com`;
    const teacher = await createAccount('teacher', email);

    // 1. Run offboarding
    const offRes = await admin.post(`/offboarding/staff/${teacher.id}`, {
      data: {
        reason: 'resigned',
        effectiveDate: '2026-07-22',
        reasonNotes: 'Teacher resignation',
      },
    });
    expect(offRes.ok(), `Offboarding staff failed: ${await offRes.text()}`).toBeTruthy();
    const offBody = await offRes.json();
    expect(offBody.record.status).toBe('completed');
    expect(offBody.record.reason).toBe('resigned');
    expect(offBody.actions.length).toBe(11);

    // 2. Confirm in former staff directory
    const formerRes = await admin.get(`/offboarding/former-staff?search=${email}`);
    expect(formerRes.ok()).toBeTruthy();
    const formerList = await formerRes.json();
    expect(formerList.length).toBe(1);
    expect(formerList[0].id).toBe(teacher.id);
  });

  test('4. cancel offboarding re-activates user account', async () => {
    const email = `stud-${uuidv4()}@school.com`;
    const student = await createAccount('student', email);

    // Offboard first
    const offRes = await admin.post(`/offboarding/students/${student.id}`, {
      data: {
        reason: 'withdrawn',
        effectiveDate: '2026-07-22',
      },
    });
    expect(offRes.ok()).toBeTruthy();
    const offBody = await offRes.json();
    const recordId = offBody.record.id;

    // Cancel offboarding
    const cancelRes = await admin.post(`/offboarding/records/${recordId}/cancel`);
    expect(cancelRes.ok(), `Cancel failed: ${await cancelRes.text()}`).toBeTruthy();
    const cancelBody = await cancelRes.json();
    expect(cancelBody.success).toBe(true);

    // Verify user record status is active again
    const userRes = await admin.get(`/users?search=${email}`);
    expect(userRes.ok()).toBeTruthy();
    const userBody = await userRes.json();
    const users = userBody.users || userBody;
    const updatedStudent = users.find((u: any) => u.id === student.id);
    expect(updatedStudent.is_active).toBe(1);
  });
});
