<?php
// =============================================================
//  include/AccountRepository.php
//
//  Application-layer unification over the three physically separate
//  account tables (users, rotation_nurses, clinical_students) — see
//  docs/account-unification-plan.md for why they stay separate tables
//  rather than being merged (overlapping ID sequences + ~15 dependent
//  foreign keys make a physical merge high-risk; this gets most of the
//  real benefit — one lookup/auth path instead of three near-identical
//  copies — without touching a single row of production data).
//
//  Existing table-specific code (list views, edit forms, anything that
//  needs a column only one table has) keeps querying its own table
//  directly. This is for the cross-cutting operations: authentication,
//  "find this email across every account type," and similar.
// =============================================================

interface Account
{
    public function getId(): int;
    public function getTable(): string;
    public function getUserType(): string;
    public function getFirstname(): string;
    public function getSurname(): string;
    public function getFullName(): string;
    public function getEmail(): string;
    public function getPhone(): ?string;
    public function getStatus(): string;
    public function getProfile(): string;
    public function getPasswordHash(): string;
    public function isPasswordChangeRequired(): bool;

    /** Raw row data, for call sites still built around the old associative-array shape. */
    public function toArray(): array;
}

abstract class AbstractAccount implements Account
{
    protected array $row;

    public function __construct(array $row)
    {
        $this->row = $row;
    }

    public function getId(): int { return (int)$this->row['id']; }
    public function getFirstname(): string { return $this->row['firstname'] ?? ''; }
    public function getSurname(): string { return $this->row['surname'] ?? ''; }
    public function getFullName(): string { return trim($this->getFirstname() . ' ' . $this->getSurname()); }
    public function getEmail(): string { return $this->row['email'] ?? ''; }
    public function getPhone(): ?string { return $this->row['phone'] ?? null; }
    public function getStatus(): string { return $this->row['status'] ?? 'Active'; }
    public function getProfile(): string { return $this->row['profile'] ?? 'default.png'; }
    public function getPasswordHash(): string { return $this->row['password'] ?? ''; }
    public function isPasswordChangeRequired(): bool { return !empty($this->row['password_change_required']); }
    public function toArray(): array { return $this->row; }

    abstract public function getTable(): string;
    abstract public function getUserType(): string;
}

final class StaffAccount extends AbstractAccount
{
    public function getTable(): string { return 'users'; }
    public function getUserType(): string { return $this->row['usertype'] ?? ''; }
    public function getRoleId(): ?int { return isset($this->row['role_id']) ? (int)$this->row['role_id'] : null; }
    public function getUsername(): ?string { return $this->row['username'] ?? null; }
    public function getGrade(): ?string { return $this->row['grade'] ?? null; }
    public function getDesignation(): ?string { return $this->row['designation'] ?? null; }
}

final class NurseAccount extends AbstractAccount
{
    public function getTable(): string { return 'rotation_nurses'; }
    public function getUserType(): string { return 'Rotation_Nurse'; }
    public function getStudentId(): ?string { return $this->row['student_id'] ?? null; }
    public function getSchoolName(): ?string { return $this->row['school_name'] ?? null; }
    public function getSpecialtyId(): ?int { return isset($this->row['specialty_id']) ? (int)$this->row['specialty_id'] : null; }
    public function getSpecialtyName(): ?string { return $this->row['specialty_name'] ?? null; }
    public function getRotationStatus(): ?string { return $this->row['rotation_status'] ?? null; }
}

final class StudentAccount extends AbstractAccount
{
    public function getTable(): string { return 'clinical_students'; }
    public function getUserType(): string { return 'Clinical_Student'; }
    public function getStudentId(): ?string { return $this->row['student_id'] ?? null; }
    public function getSchoolName(): ?string { return $this->row['school_name'] ?? null; }
    public function getLevel(): ?string { return $this->row['level'] ?? null; }
    public function getProgramme(): ?string { return $this->row['programme'] ?? null; }
    public function getSupervisorName(): ?string { return $this->row['supervisor_name'] ?? null; }
    public function getRotationStatus(): ?string { return $this->row['rotation_status'] ?? null; }
}

final class AccountRepository
{
    private mysqli $conn;

    /** Column lists matched to what each table actually has — see the schema dump this was built from. */
    private const TABLE_COLUMNS = [
        'users' => "id, firstname, surname, designation, email, phone, gender, password, profile,
                    usertype, role_id, username, grade, password_change_required, status, last_login",
        'rotation_nurses' => "id, firstname, surname, email, phone, gender, password, profile,
                    password_change_required, student_id, school_name, specialty_id, specialty_name,
                    rotation_status, status, last_login",
        'clinical_students' => "id, firstname, surname, email, phone, gender, password, profile,
                    password_change_required, student_id, school_name, level, programme,
                    supervisor_name, rotation_status, status, last_login",
    ];

    // Search order matters where email could theoretically collide across tables (it shouldn't,
    // given checkGlobalDuplicate() in account_helpers.php enforces global uniqueness on creation,
    // but that check isn't a DB-level constraint — so order is: staff, then nurse, then student).
    private const SEARCH_ORDER = ['users', 'rotation_nurses', 'clinical_students'];

    public function __construct(mysqli $conn)
    {
        $this->conn = $conn;
    }

    /**
     * Verifies email + password against all three tables (Active accounts only).
     * Returns null on any failure — unknown email, wrong password, or inactive account —
     * deliberately without distinguishing which, so callers can't be used for account enumeration.
     */
    public function authenticate(string $email, string $password): ?Account
    {
        $account = $this->findActiveByEmail($email);
        if (!$account) return null;
        if (!password_verify($password, $account->getPasswordHash())) return null;
        return $account;
    }

    /** Finds an Active account by email across all three tables. */
    public function findActiveByEmail(string $email): ?Account
    {
        return $this->searchByEmail($email, true);
    }

    /** Finds an account by email regardless of status — used for "your account is inactive" messaging. */
    public function findByEmailAnyStatus(string $email): ?Account
    {
        return $this->searchByEmail($email, false);
    }

    private function searchByEmail(string $email, bool $activeOnly): ?Account
    {
        foreach (self::SEARCH_ORDER as $table) {
            $cols = self::TABLE_COLUMNS[$table];
            $sql  = "SELECT {$cols} FROM `{$table}` WHERE email = ?" . ($activeOnly ? " AND status = 'Active'" : '');
            $stmt = $this->conn->prepare($sql);
            if (!$stmt) continue;
            $stmt->bind_param('s', $email);
            $stmt->execute();
            $row = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            if ($row) return $this->wrap($table, $row);
        }
        return null;
    }

    public function findById(string $table, int $id): ?Account
    {
        if (!isset(self::TABLE_COLUMNS[$table])) return null;
        $cols = self::TABLE_COLUMNS[$table];
        $stmt = $this->conn->prepare("SELECT {$cols} FROM `{$table}` WHERE id = ?");
        if (!$stmt) return null;
        $stmt->bind_param('i', $id);
        $stmt->execute();
        $row = $stmt->get_result()->fetch_assoc();
        $stmt->close();
        return $row ? $this->wrap($table, $row) : null;
    }

    private function wrap(string $table, array $row): Account
    {
        return match ($table) {
            'users'             => new StaffAccount($row),
            'rotation_nurses'   => new NurseAccount($row),
            'clinical_students' => new StudentAccount($row),
            default             => throw new InvalidArgumentException("Unknown account table: {$table}"),
        };
    }
}
