const { pointAtDevDb } = require('./setup');
const Database = require('better-sqlite3');
const { v4: uuidv4 } = require('uuid');

pointAtDevDb();

const dbPath = process.env.DB_PATH;
const getDb = () => {
  const db = new Database(dbPath);
  db.pragma('foreign_keys = ON');
  return db;
};

// Import the service
const OffboardingService = require('../src/services/OffboardingService');

describe('OffboardingService pipeline', () => {
  let db;

  beforeAll(() => {
    db = getDb();
  });

  afterAll(() => {
    db.close();
  });

  // Helper to create a user
  const createUser = (role, email = `test-${uuidv4()}@school.com`) => {
    const uid = uuidv4();
    const result = db.prepare(`
      INSERT INTO users (uid, email, password, role, first_name, last_name, is_active)
      VALUES (?, ?, 'hash', ?, 'Test', 'User', 1)
    `).run(uid, email, role);
    return { id: result.lastInsertRowid, uid, email };
  };

  it('student offboarding happy path (no fees, no hostel, no transport)', () => {
    const student = createUser('student');
    const admin = createUser('school_admin');

    const result = OffboardingService.offboardStudent(student.id, {
      reason: 'graduated',
      effectiveDate: '2026-07-22',
      reasonNotes: 'Happy path test'
    }, admin.id);

    expect(result.record).toBeDefined();
    expect(result.record.user_id).toBe(student.id);
    expect(result.record.reason).toBe('graduated');
    expect(result.record.alumni_status).toBe('active_alumni');
    expect(result.record.status).toBe('completed');

    expect(result.actions.length).toBe(11);
    
    // Check all actions are completed
    result.actions.forEach(action => {
      expect(action.status).toBe('completed');
    });

    // Verify user is inactive and archived
    const updatedUser = db.prepare('SELECT is_active, archive_status FROM users WHERE id = ?').get(student.id);
    expect(updatedUser.is_active).toBe(0);
    expect(updatedUser.archive_status).toBe('active_alumni');

    // Verify audit log exists
    const audit = db.prepare("SELECT * FROM audit_logs WHERE action = 'STUDENT_OFFBOARD' AND entity_id = ?").get(student.uid);
    expect(audit).toBeDefined();
  });

  it('student offboarding blocked by outstanding fees without override', () => {
    const student = createUser('student');
    const admin = createUser('school_admin');

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

    // Call without override
    expect(() => {
      OffboardingService.offboardStudent(student.id, {
        reason: 'withdrawn',
        effectiveDate: '2026-07-22'
      }, admin.id);
    }).toThrow(/outstanding fees/i);

    // Verify student is still active
    const user = db.prepare('SELECT is_active, archive_status FROM users WHERE id = ?').get(student.id);
    expect(user.is_active).toBe(1);
    expect(user.archive_status).toBeNull();
  });

  it('student offboarding succeeds with outstanding fees when overridden', () => {
    const student = createUser('student');
    const admin = createUser('school_admin');

    // Add fee
    const feeGroupUid = uuidv4();
    const groupResult = db.prepare("INSERT INTO fee_groups (uid, name, amount) VALUES (?, 'Test Tuition 2', 200)").run(feeGroupUid);
    db.prepare(`
      INSERT INTO student_fees (uid, student_id, fee_group_id, amount, paid_amount, status)
      VALUES (?, ?, ?, 200, 0, 'pending')
    `).run(uuidv4(), student.id, groupResult.lastInsertRowid);

    // Call with override
    const result = OffboardingService.offboardStudent(student.id, {
      reason: 'withdrawn',
      effectiveDate: '2026-07-22',
      override: {
        reason: 'Financial aid transition'
      }
    }, admin.id);

    expect(result.record.status).toBe('completed');
    
    // Check fee action notes
    const feeAction = result.actions.find(a => a.step === 'fee_settled');
    expect(feeAction.status).toBe('completed');
    expect(feeAction.notes).toContain('Financial aid transition');

    // Verify audit log has the override log
    const overrideAudit = db.prepare("SELECT * FROM audit_logs WHERE action = 'FEE_OVERRIDE' AND entity_id = ?").get(student.uid);
    expect(overrideAudit).toBeDefined();
  });

  it('staff offboarding happy path (no leave, no payroll, no assignments)', () => {
    const teacher = createUser('teacher');
    const admin = createUser('school_admin');

    const result = OffboardingService.offboardStaff(teacher.id, {
      reason: 'resigned',
      effectiveDate: '2026-07-22',
      reasonNotes: 'Resignation accepted'
    }, admin.id);

    expect(result.record).toBeDefined();
    expect(result.record.user_id).toBe(teacher.id);
    expect(result.record.reason).toBe('resigned');
    expect(result.record.status).toBe('completed');

    expect(result.actions.length).toBe(11);
    result.actions.forEach(action => {
      expect(action.status).toBe('completed');
    });

    const updatedUser = db.prepare('SELECT is_active, archive_status FROM users WHERE id = ?').get(teacher.id);
    expect(updatedUser.is_active).toBe(0);
    expect(updatedUser.archive_status).toBe('former_staff');
  });

  it('staff offboarding blocked by unpaid payroll without override', () => {
    const teacher = createUser('teacher');
    const admin = createUser('school_admin');

    // Add payroll run and payslip
    const runResult = db.prepare(`
      INSERT INTO payroll_runs (uid, period_month, period_year, status)
      VALUES (?, 7, 2026, 'approved')
    `).run(uuidv4());

    db.prepare(`
      INSERT INTO payslips (uid, staff_id, payroll_run_id, basic_salary, gross_salary, net_salary, status)
      VALUES (?, ?, ?, 2000, 2000, 1800, 'approved')
    `).run(uuidv4(), teacher.id, runResult.lastInsertRowid);

    // Call without override
    expect(() => {
      OffboardingService.offboardStaff(teacher.id, {
        reason: 'terminated',
        effectiveDate: '2026-07-22'
      }, admin.id);
    }).toThrow(/outstanding payroll/i);
  });

  it('staff offboarding clears classes and subjects', () => {
    const teacher = createUser('teacher');
    const admin = createUser('school_admin');

    // Create a class and subject assigned to this teacher
    const classResult = db.prepare(`
      INSERT INTO classes (uid, name, class_teacher_id)
      VALUES (?, 'Grade 4A', ?)
    `).run(uuidv4(), teacher.id);

    const subjectResult = db.prepare(`
      INSERT INTO subjects (uid, name, class_id, teacher_id)
      VALUES (?, 'Maths', ?, ?)
    `).run(uuidv4(), classResult.lastInsertRowid, teacher.id);

    // Run offboarding
    OffboardingService.offboardStaff(teacher.id, {
      reason: 'retired',
      effectiveDate: '2026-07-22'
    }, admin.id);

    // Verify assignments are cleared
    const updatedClass = db.prepare('SELECT class_teacher_id FROM classes WHERE id = ?').get(classResult.lastInsertRowid);
    expect(updatedClass.class_teacher_id).toBeNull();

    const updatedSubject = db.prepare('SELECT teacher_id FROM subjects WHERE id = ?').get(subjectResult.lastInsertRowid);
    expect(updatedSubject.teacher_id).toBeNull();
  });

  it('pipeline transaction integrity (rollback on intermediate step failure)', () => {
    const student = createUser('student');
    const admin = createUser('school_admin');

    // We will cause a constraint violation or database error during deactivation
    // by passing an invalid student ID to a mock step. Wait, let's just make the user_id invalid or
    // pass a non-existent student ID. Let's see: if we pass a userId that does not exist to a private query,
    // or let's mock a database throw. Actually, we can trigger a check constraint failure.
    // For example, in offboardStudent, if we pass an invalid reason that fails the CHECK constraint on offboarding_records:
    // reason must be graduated, transferred, withdrawn, expelled, resigned, terminated, retired, deceased, other.
    // If we pass 'invalid_reason_string', the INSERT INTO offboarding_records will fail.
    // Wait, let's see if the student remains active!
    // Actually, we do select check:
    // If we pass an invalid reason, the transaction throws on insert and rolls back. But let's verify if a mid-pipeline failure rolls back.
    // For example, let's make Step 2 or 10 throw by setting a row constraint.
    // Or we can just test that the student status does NOT change if we pass an invalid reason.
    // Wait, if reason is invalid, it throws at offboarding_records insert. Since it's the very first step, the rest won't execute anyway.
    // How about throwing in the middle, say, after updating enrollments?
    // Let's create a situation where a unique constraint fails on insertion of an action or something.
    // Wait! In `enrollment_closed` step, we run `UPDATE enrollments SET status = ? WHERE id = ?`.
    // If we pass `payload.enrollmentStatus = 'VERY_LONG_INVALID_STATUS_THAT_FAILS_CHECK_CONSTRAINT'`, the check constraint on enrollments.status will fail!
    // Let's verify enrollments.status check constraint:
    // status CHECK(status IN ('active', 'inactive', 'transferred', 'graduated'))
    // If we pass `payload.enrollmentStatus = 'invalid_status_value'`, it will throw a constraint error during the update of enrollments!
    // Since it updates enrollments before deactivating user, if it throws, the transaction rolls back, and the student should remain active!
    // Let's verify this!

    // First create a class and an enrollment for this student
    const classResult = db.prepare(`
      INSERT INTO classes (uid, name) VALUES (?, 'Grade Test Rollback')
    `).run(uuidv4());
    const enrollResult = db.prepare(`
      INSERT INTO enrollments (uid, student_id, class_id, status)
      VALUES (?, ?, ?, 'active')
    `).run(uuidv4(), student.id, classResult.lastInsertRowid);

    // Call offboarding with invalid enrollment status
    expect(() => {
      OffboardingService.offboardStudent(student.id, {
        reason: 'other',
        enrollmentStatus: 'super_invalid_status_value', // Fails CHECK constraint on enrollments.status
        effectiveDate: '2026-07-22'
      }, admin.id);
    }).toThrow();

    // Verify student is still active (is_active is 1)
    const user = db.prepare('SELECT is_active, archive_status FROM users WHERE id = ?').get(student.id);
    expect(user.is_active).toBe(1);
    expect(user.archive_status).toBeNull();

    // Verify enrollment remains 'active'
    const enroll = db.prepare('SELECT status FROM enrollments WHERE id = ?').get(enrollResult.lastInsertRowid);
    expect(enroll.status).toBe('active');

    // Verify no offboarding record was created
    const record = db.prepare('SELECT count(*) as count FROM offboarding_records WHERE user_id = ?').get(student.id);
    expect(record.count).toBe(0);
  });
});
