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

const dbPath = process.env.DB_PATH || path.join(__dirname, '../../data/school.db');
let db;
const getDb = () => {
  if (!db) {
    db = new Database(dbPath);
    db.pragma('foreign_keys = ON');
  }
  return db;
};

class OffboardingService {
  /**
   * Run the student offboarding pipeline in a single transaction.
   */
  static offboardStudent(userId, payload, initiatedBy) {
    const database = getDb();
    
    // Find student
    const student = database.prepare('SELECT * FROM users WHERE id = ? AND is_deleted = 0').get(userId);
    if (!student) {
      throw new Error('User not found');
    }
    if (student.role !== 'student') {
      throw new Error('User is not a student');
    }

    const recordUid = uuidv4();
    const effectiveDate = payload.effectiveDate || new Date().toISOString().split('T')[0];
    const destination = payload.destination || null;
    const reasonNotes = payload.reasonNotes || null;
    const reason = payload.reason || 'withdrawn';
    const alumniStatus = reason === 'expelled' ? 'inactive_alumni' : 'active_alumni';

    // Steps to execute
    const steps = [
      'enrollment_closed', 'hostel_released', 'transport_removed',
      'clubs_removed', 'fee_settled', 'leave_closed', 'payroll_finalized',
      'class_assignments_cleared', 'subject_assignments_cleared',
      'account_deactivated', 'record_archived'
    ];

    const pipeline = database.transaction(() => {
      // 1. Insert offboarding record
      const recordResult = database.prepare(`
        INSERT INTO offboarding_records (
          uid, user_id, audience, reason, reason_notes, initiated_by, 
          status, effective_date, destination, alumni_status, sync_status
        )
        VALUES (?, ?, 'student', ?, ?, ?, 'completed', ?, ?, ?, 'pending')
      `).run(recordUid, userId, reason, reasonNotes, initiatedBy, effectiveDate, destination, alumniStatus);

      const recordId = recordResult.lastInsertRowid;

      // Helper to insert action
      const writeAction = (step, status, notes = null) => {
        database.prepare(`
          INSERT INTO offboarding_actions (
            uid, record_id, step, status, notes, performed_by, sync_status
          )
          VALUES (?, ?, ?, ?, ?, ?, 'pending')
        `).run(uuidv4(), recordId, step, status, notes, initiatedBy);
      };

      // Execute Step 1: enrollment_closed
      try {
        const enrollments = database.prepare('SELECT id FROM enrollments WHERE student_id = ? AND is_deleted = 0').all(userId);
        let targetStatus = 'inactive';
        if (reason === 'graduated') targetStatus = 'graduated';
        else if (reason === 'transferred') targetStatus = 'transferred';
        else if (payload.enrollmentStatus) targetStatus = payload.enrollmentStatus;

        for (const enroll of enrollments) {
          database.prepare(`
            UPDATE enrollments 
            SET status = ?, sync_status = 'pending', updated_at = datetime('now', 'localtime') 
            WHERE id = ?
          `).run(targetStatus, enroll.id);
        }
        writeAction('enrollment_closed', 'completed', `Closed ${enrollments.length} enrollments to status ${targetStatus}`);
      } catch (e) {
        writeAction('enrollment_closed', 'failed', e.message);
        throw e;
      }

      // Execute Step 2: hostel_released
      try {
        const assignments = database.prepare("SELECT id, room_id FROM room_assignments WHERE student_id = ? AND status = 'active' AND is_deleted = 0").all(userId);
        for (const assign of assignments) {
          database.prepare(`
            UPDATE room_assignments 
            SET status = 'inactive', end_date = date('now', 'localtime'), sync_status = 'pending', updated_at = datetime('now', 'localtime') 
            WHERE id = ?
          `).run(assign.id);
          
          database.prepare(`
            UPDATE rooms 
            SET status = 'available', sync_status = 'pending', updated_at = datetime('now', 'localtime') 
            WHERE id = ?
          `).run(assign.room_id);
        }
        writeAction('hostel_released', 'completed', `Released ${assignments.length} hostel room assignments`);
      } catch (e) {
        writeAction('hostel_released', 'failed', e.message);
        throw e;
      }

      // Execute Step 3: transport_removed
      try {
        const result = database.prepare(`
          UPDATE transport_allocations 
          SET is_active = 0, is_deleted = 1, sync_status = 'pending', updated_at = datetime('now', 'localtime') 
          WHERE student_id = ? AND is_active = 1 AND is_deleted = 0
        `).run(userId);
        writeAction('transport_removed', 'completed', `Removed from transport routes. Affected ${result.changes} routes.`);
      } catch (e) {
        writeAction('transport_removed', 'failed', e.message);
        throw e;
      }

      // Execute Step 4: clubs_removed
      try {
        const memResult = database.prepare(`
          UPDATE club_memberships 
          SET status = 'inactive', sync_status = 'pending', updated_at = datetime('now', 'localtime') 
          WHERE student_id = ? AND status = 'active' AND is_deleted = 0
        `).run(userId);
        
        const attResult = database.prepare(`
          UPDATE club_attendance 
          SET is_deleted = 1, sync_status = 'pending', updated_at = datetime('now', 'localtime') 
          WHERE student_id = ? AND is_deleted = 0
        `).run(userId);

        writeAction('clubs_removed', 'completed', `Removed from clubs: memberships inactive (${memResult.changes}), attendance soft-deleted (${attResult.changes})`);
      } catch (e) {
        writeAction('clubs_removed', 'failed', e.message);
        throw e;
      }

      // Execute Step 5: fee_settled
      try {
        const feeRow = database.prepare(`
          SELECT COALESCE(SUM(amount - paid_amount - discount_amount + fine_amount), 0) AS outstanding 
          FROM student_fees 
          WHERE student_id = ? AND is_deleted = 0
        `).get(userId);

        const outstanding = Math.round((feeRow.outstanding || 0) * 100) / 100;
        if (outstanding > 0) {
          if (payload.override && payload.override.reason) {
            writeAction('fee_settled', 'completed', `Overridden: ${payload.override.reason} (Outstanding: $${outstanding})`);
          } else {
            throw new Error(`Outstanding fees of $${outstanding} must be settled or overridden`);
          }
        } else {
          writeAction('fee_settled', 'completed', 'No outstanding fees');
        }
      } catch (e) {
        writeAction('fee_settled', 'failed', e.message);
        throw e;
      }

      // Execute Step 6: leave_closed (Not applicable to students)
      writeAction('leave_closed', 'completed', 'Not applicable to students');

      // Execute Step 7: payroll_finalized (Not applicable to students)
      writeAction('payroll_finalized', 'completed', 'Not applicable to students');

      // Execute Step 8: class_assignments_cleared (Not applicable to students)
      writeAction('class_assignments_cleared', 'completed', 'Not applicable to students');

      // Execute Step 9: subject_assignments_cleared (Not applicable to students)
      writeAction('subject_assignments_cleared', 'completed', 'Not applicable to students');

      // Execute Step 10: account_deactivated
      try {
        database.prepare(`
          UPDATE users 
          SET is_active = 0, sync_status = 'pending', updated_at = datetime('now', 'localtime') 
          WHERE id = ?
        `).run(userId);
        writeAction('account_deactivated', 'completed', 'User account login deactivated');
      } catch (e) {
        writeAction('account_deactivated', 'failed', e.message);
        throw e;
      }

      // Execute Step 11: record_archived
      try {
        database.prepare(`
          UPDATE users 
          SET archive_status = ?, sync_status = 'pending', updated_at = datetime('now', 'localtime') 
          WHERE id = ?
        `).run(alumniStatus, userId);
        writeAction('record_archived', 'completed', `Student archived to alumni status: ${alumniStatus}`);
      } catch (e) {
        writeAction('record_archived', 'failed', e.message);
        throw e;
      }

      // 8. Write Audit Log
      AuditService.log(null, 'STUDENT_OFFBOARD', {
        db: database,
        userId: initiatedBy,
        entityType: 'user',
        entityId: student.uid,
        oldData: { is_active: student.is_active, archive_status: student.archive_status },
        newData: { is_active: 0, archive_status: alumniStatus, offboarding_reason: reason }
      });

      if (payload.override && payload.override.reason && outstandingRowNeeded(userId, database)) {
        AuditService.log(null, 'FEE_OVERRIDE', {
          db: database,
          userId: initiatedBy,
          entityType: 'user',
          entityId: student.uid,
          newData: { override_reason: payload.override.reason }
        });
      }

      // Return details
      const record = database.prepare('SELECT * FROM offboarding_records WHERE id = ?').get(recordId);
      const actions = database.prepare('SELECT * FROM offboarding_actions WHERE record_id = ?').all(recordId);
      return { record, actions };
    });

    return pipeline();
  }

  /**
   * Run the staff offboarding pipeline in a single transaction.
   */
  static offboardStaff(userId, payload, initiatedBy) {
    const database = getDb();
    
    // Find staff
    const staff = database.prepare('SELECT * FROM users WHERE id = ? AND is_deleted = 0').get(userId);
    if (!staff) {
      throw new Error('User not found');
    }
    const staffRoles = ['systems_admin', 'school_admin', 'teacher', 'principal', 'hr', 'bursar', 'librarian', 'clubs_head', 'nurse', 'dining_staff', 'driver', 'groundsman', 'matron', 'boarding_master', 'security', 'janitor'];
    if (!staffRoles.includes(staff.role)) {
      throw new Error('User is not a staff member');
    }

    const recordUid = uuidv4();
    const effectiveDate = payload.effectiveDate || new Date().toISOString().split('T')[0];
    const reasonNotes = payload.reasonNotes || null;
    const reason = payload.reason || 'resigned';

    const pipeline = database.transaction(() => {
      // 1. Insert offboarding record
      const recordResult = database.prepare(`
        INSERT INTO offboarding_records (
          uid, user_id, audience, reason, reason_notes, initiated_by, 
          status, effective_date, sync_status
        )
        VALUES (?, ?, 'staff', ?, ?, ?, 'completed', ?, 'pending')
      `).run(recordUid, userId, reason, reasonNotes, initiatedBy, effectiveDate);

      const recordId = recordResult.lastInsertRowid;

      // Helper to insert action
      const writeAction = (step, status, notes = null) => {
        database.prepare(`
          INSERT INTO offboarding_actions (
            uid, record_id, step, status, notes, performed_by, sync_status
          )
          VALUES (?, ?, ?, ?, ?, ?, 'pending')
        `).run(uuidv4(), recordId, step, status, notes, initiatedBy);
      };

      // Execute non-applicable student steps as completed
      writeAction('enrollment_closed', 'completed', 'Not applicable to staff');
      writeAction('hostel_released', 'completed', 'Not applicable to staff');
      writeAction('transport_removed', 'completed', 'Not applicable to staff');
      writeAction('clubs_removed', 'completed', 'Not applicable to staff');
      writeAction('fee_settled', 'completed', 'Not applicable to staff');

      // Execute Step 6: leave_closed
      try {
        const leaveResult = database.prepare(`
          UPDATE leave_requests 
          SET status = 'cancelled', rejection_reason = 'Auto-cancelled on offboarding', sync_status = 'pending', updated_at = datetime('now', 'localtime') 
          WHERE staff_id = ? AND status = 'pending' AND is_deleted = 0
        `).run(userId);
        writeAction('leave_closed', 'completed', `Cancelled pending leave requests. Count: ${leaveResult.changes}`);
      } catch (e) {
        writeAction('leave_closed', 'failed', e.message);
        throw e;
      }

      // Execute Step 7: payroll_finalized
      try {
        const latestPayslip = database.prepare(`
          SELECT p.*, r.period_month, r.period_year
          FROM payslips p
          JOIN payroll_runs r ON p.payroll_run_id = r.id
          WHERE p.staff_id = ? AND p.is_deleted = 0 AND p.status != 'cancelled'
          ORDER BY r.period_year DESC, r.period_month DESC
          LIMIT 1
        `).get(userId);

        if (latestPayslip && latestPayslip.status !== 'paid') {
          if (payload.override && payload.override.reason) {
            writeAction('payroll_finalized', 'completed', `Overridden: ${payload.override.reason} (Unpaid Net: $${latestPayslip.net_salary})`);
          } else {
            throw new Error(`Outstanding payroll in period ${latestPayslip.period_year}-${latestPayslip.period_month} of $${latestPayslip.net_salary} must be settled or overridden`);
          }
        } else {
          writeAction('payroll_finalized', 'completed', 'No outstanding payroll runs');
        }
      } catch (e) {
        writeAction('payroll_finalized', 'failed', e.message);
        throw e;
      }

      // Execute Step 8: class_assignments_cleared
      try {
        const classResult = database.prepare(`
          UPDATE classes 
          SET class_teacher_id = NULL, sync_status = 'pending', updated_at = datetime('now', 'localtime') 
          WHERE class_teacher_id = ?
        `).run(userId);
        writeAction('class_assignments_cleared', 'completed', `Unassigned from classes as class teacher. Count: ${classResult.changes}`);
      } catch (e) {
        writeAction('class_assignments_cleared', 'failed', e.message);
        throw e;
      }

      // Execute Step 9: subject_assignments_cleared
      try {
        const subjectResult = database.prepare(`
          UPDATE subjects 
          SET teacher_id = NULL, sync_status = 'pending', updated_at = datetime('now', 'localtime') 
          WHERE teacher_id = ?
        `).run(userId);
        
        // Revoke user roles from Phase 1
        let roleChanges = 0;
        try {
          const roleResult = database.prepare(`
            UPDATE user_roles 
            SET revoked_by = ?, revoked_at = datetime('now', 'localtime'), revoke_reason = 'Staff offboarded via wizard', sync_status = 'pending', updated_at = datetime('now', 'localtime') 
            WHERE user_id = ? AND revoked_at IS NULL AND is_deleted = 0
          `).run(initiatedBy, userId);
          roleChanges = roleResult.changes;
        } catch (roleErr) {
          // If table user_roles doesn't exist yet (e.g. before Phase 1 table is loaded)
          console.warn('[offboarding] Failed to update user_roles table:', roleErr.message);
        }

        writeAction('subject_assignments_cleared', 'completed', `Unassigned from subjects (${subjectResult.changes}) and revoked active auxiliary roles (${roleChanges})`);
      } catch (e) {
        writeAction('subject_assignments_cleared', 'failed', e.message);
        throw e;
      }

      // Execute Step 10: account_deactivated
      try {
        database.prepare(`
          UPDATE users 
          SET is_active = 0, sync_status = 'pending', updated_at = datetime('now', 'localtime') 
          WHERE id = ?
        `).run(userId);
        writeAction('account_deactivated', 'completed', 'User account login deactivated');
      } catch (e) {
        writeAction('account_deactivated', 'failed', e.message);
        throw e;
      }

      // Execute Step 11: record_archived
      try {
        database.prepare(`
          UPDATE users 
          SET archive_status = 'former_staff', sync_status = 'pending', updated_at = datetime('now', 'localtime') 
          WHERE id = ?
        `).run(userId);
        writeAction('record_archived', 'completed', 'Staff archived to former_staff status');
      } catch (e) {
        writeAction('record_archived', 'failed', e.message);
        throw e;
      }

      // 8. Write Audit Log
      AuditService.log(null, 'STAFF_OFFBOARD', {
        db: database,
        userId: initiatedBy,
        entityType: 'user',
        entityId: staff.uid,
        oldData: { is_active: staff.is_active, archive_status: staff.archive_status },
        newData: { is_active: 0, archive_status: 'former_staff', offboarding_reason: reason }
      });

      if (payload.override && payload.override.reason) {
        AuditService.log(null, 'PAYROLL_OVERRIDE', {
          db: database,
          userId: initiatedBy,
          entityType: 'user',
          entityId: staff.uid,
          newData: { override_reason: payload.override.reason }
        });
      }

      // Return details
      const record = database.prepare('SELECT * FROM offboarding_records WHERE id = ?').get(recordId);
      const actions = database.prepare('SELECT * FROM offboarding_actions WHERE record_id = ?').all(recordId);
      return { record, actions };
    });

    return pipeline();
  }
}

function outstandingRowNeeded(userId, database) {
  try {
    const feeRow = database.prepare(`
      SELECT COALESCE(SUM(amount - paid_amount - discount_amount + fine_amount), 0) AS outstanding 
      FROM student_fees 
      WHERE student_id = ? AND is_deleted = 0
    `).get(userId);
    return (feeRow?.outstanding || 0) > 0;
  } catch (e) {
    return false;
  }
}

module.exports = OffboardingService;
