<?php

declare(strict_types=1);

namespace App\Models;

use App\Enums\EmployeeStatus;
use App\Models\Concerns\Auditable;
use App\Models\Concerns\TracksBlameable;
use Database\Factories\EmployeeFactory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;

/**
 * A person assets can be issued to.
 *
 * Not every employee has a login — most won't — so this is not subordinate to
 * `User`; the optional link runs the other way (`User::employee()`).
 *
 * "Clearance" — chasing outstanding assets from an exiting employee — is
 * deliberately absent from this model; it is a workflow built on top of
 * `assignments()`, not a relation of its own, and arrives in a later phase.
 *
 * @property int $id
 * @property string $employee_code
 * @property string $first_name
 * @property string|null $last_name
 * @property string|null $email
 * @property string|null $personal_email
 * @property string|null $phone
 * @property int|null $team_id
 * @property string|null $designation
 * @property string|null $squad
 * @property int|null $location_id
 * @property EmployeeStatus $status
 * @property Carbon|null $date_of_joining
 * @property Carbon|null $date_of_exit
 * @property string|null $notes
 */
class Employee extends Model
{
    /** @use HasFactory<EmployeeFactory> */
    use Auditable, HasFactory, SoftDeletes, TracksBlameable;

    /** @var list<string> */
    protected $fillable = [
        'employee_code',
        'first_name',
        'last_name',
        'email',
        'personal_email',
        'phone',
        'team_id',
        'designation',
        'squad',
        'location_id',
        'status',
        'date_of_joining',
        'date_of_exit',
        'notes',
    ];

    /**
     * @return BelongsTo<Team, $this>
     */
    public function team(): BelongsTo
    {
        return $this->belongsTo(Team::class);
    }

    /**
     * @return BelongsTo<Location, $this>
     */
    public function location(): BelongsTo
    {
        return $this->belongsTo(Location::class);
    }

    /**
     * Teams this employee is recorded as the head of.
     *
     * A `hasMany` rather than `hasOne`: nothing stops the same person heading
     * two teams, and the relation should not silently hide that.
     *
     * @return HasMany<Team, $this>
     */
    public function teamsHeaded(): HasMany
    {
        return $this->hasMany(Team::class, 'head_employee_id');
    }

    /**
     * The login account linked to this employee, if any.
     *
     * @return HasOne<User, $this>
     */
    public function user(): HasOne
    {
        return $this->hasOne(User::class);
    }

    /**
     * Every custody period this employee has ever been party to, newest
     * first — the complete assignment history shown on the employee page.
     *
     * @return HasMany<AssetAssignment, $this>
     */
    public function assignments(): HasMany
    {
        return $this->hasMany(AssetAssignment::class)->latest('assigned_at');
    }

    /**
     * Custody periods currently open — what this employee holds right now.
     *
     * @return HasMany<AssetAssignment, $this>
     */
    public function activeAssignments(): HasMany
    {
        return $this->hasMany(AssetAssignment::class)->active();
    }

    /**
     * The assets this employee currently holds. `activeAssignments()` is
     * preferred wherever the issue date or condition is also needed — this
     * exists for the places that only care about the assets themselves.
     *
     * @return HasManyThrough<Asset, AssetAssignment, $this>
     */
    public function currentAssets(): HasManyThrough
    {
        return $this->hasManyThrough(
            Asset::class,
            AssetAssignment::class,
            'employee_id',
            'id',
            'id',
            'asset_id',
        )->whereNull('asset_assignments.returned_at');
    }

    /**
     * "Priya Sharma", or just "Priya" when there is no last name on file.
     */
    public function fullName(): string
    {
        return trim($this->first_name.' '.($this->last_name ?? ''));
    }

    /**
     * Initials for an avatar, e.g. "Priya Sharma" -> "PS".
     */
    public function initials(): string
    {
        $first = mb_substr($this->first_name, 0, 1);
        $last = $this->last_name !== null && $this->last_name !== ''
            ? mb_substr($this->last_name, 0, 1)
            : '';

        return mb_strtoupper($first.$last) ?: '?';
    }

    /**
     * @param  Builder<Employee>  $query
     */
    public function scopeSearch(Builder $query, ?string $term): void
    {
        $term = trim((string) $term);

        if ($term === '') {
            return;
        }

        $query->where(function (Builder $inner) use ($term): void {
            $inner->where('employee_code', 'like', '%'.$term.'%')
                ->orWhere('first_name', 'like', '%'.$term.'%')
                ->orWhere('last_name', 'like', '%'.$term.'%')
                ->orWhere('email', 'like', '%'.$term.'%')
                ->orWhere('designation', 'like', '%'.$term.'%')
                ->orWhere('squad', 'like', '%'.$term.'%')
                ->orWhereRaw("CONCAT(first_name, ' ', COALESCE(last_name, '')) like ?", ['%'.$term.'%']);
        });
    }

    /**
     * @param  Builder<Employee>  $query
     */
    public function scopeStatus(Builder $query, ?string $status): void
    {
        if ($status === null || $status === '') {
            return;
        }

        $query->where('status', $status);
    }

    /**
     * @param  Builder<Employee>  $query
     */
    public function scopeInTeam(Builder $query, ?int $teamId): void
    {
        if ($teamId === null) {
            return;
        }

        $query->where('team_id', $teamId);
    }

    /**
     * @param  Builder<Employee>  $query
     */
    public function scopeInSquad(Builder $query, ?string $squad): void
    {
        if ($squad === null || $squad === '') {
            return;
        }

        $query->where('squad', $squad);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'status' => EmployeeStatus::class,
            'date_of_joining' => 'date',
            'date_of_exit' => 'date',
        ];
    }

    protected function auditLabel(): string
    {
        return $this->fullName() !== '' ? $this->fullName() : $this->employee_code;
    }
}
