<?php

namespace App\Models;

use App\Models\Traits\HasAuditFields;
use App\Models\Traits\HasScopes;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

/**
 * Employee Details Model
 *
 * Represents employee master data including personal info, department, and designation.
 * Tracks leave history, route card assignments, and availability.
 */
class EmployeeDetails extends Model
{
    use SoftDeletes, HasAuditFields, HasScopes;

    protected $table = 'employees';

    /**
     * Status constants
     */
    public const STATUS_ACTIVE = 'active';
    public const STATUS_INACTIVE = 'inactive';
    public const STATUS_ON_LEAVE = 'on_leave';
    public const STATUS_TERMINATED = 'terminated';

    /**
     * Category constants
     */
    public const CATEGORY_PERMANENT = 'permanent';
    public const CATEGORY_CONTRACT = 'contract';
    public const CATEGORY_TRAINEE = 'trainee';
    public const CATEGORY_INTERN = 'intern';

    /**
     * Gender constants
     */
    public const GENDER_MALE = 'male';
    public const GENDER_FEMALE = 'female';
    public const GENDER_OTHER = 'other';

    protected $fillable = [
        'employee_id',
        'name',
        'gender',
        'department',
        'designation',
        'dob',
        'doj',
        'mobile',
        'aadhar_number',
        'category',
        'status',
        'profile_picture',
        'created_by',
        'updated_by',
    ];

    protected $casts = [
        'dob' => 'date',
        'doj' => 'date',
    ];

    protected $appends = ['availability', 'available_by'];

    /**
     * Column configuration for HasScopes trait
     */
    protected string $dateColumn = 'doj';

    // =========================================================================
    // RELATIONSHIPS
    // =========================================================================

    public function leaves()
    {
        return $this->hasMany(EmployeeLeave::class, 'employee_id', 'id');
    }

    public function attendances()
    {
        return $this->hasMany(EmployeeAttendance::class, 'employee_id', 'employee_id');
    }

    public function dailyReports()
    {
        return $this->hasMany(EmployeeDailyReport::class, 'employee_id', 'employee_id');
    }

    public function salaryStructure()
    {
        return $this->hasOne(EmployeeSalaryStructure::class, 'employee_id');
    }

    public function salaryDetails()
    {
        return $this->hasMany(EmployeeSalaryDetail::class, 'employee_id');
    }

    public function leaveBalances()
    {
        return $this->hasMany(EmployeeLeaveBalance::class, 'employee_id');
    }

    public function routeCardProcesses()
    {
        return $this->hasMany(RouteCardProcess::class, 'operator', 'id');
    }

    public function processes()
    {
        return $this->belongsToMany(Process::class, 'process_operator', 'employee_id', 'process_id');
    }

    // =========================================================================
    // SCOPES
    // =========================================================================

    /**
     * Scope to search employees by name, employee_id, or mobile
     */
    public function scopeSearchEmployee(Builder $query, string $term): Builder
    {
        return $query->where(function ($q) use ($term) {
            $q->where('name', 'LIKE', "%{$term}%")
              ->orWhere('employee_id', 'LIKE', "%{$term}%")
              ->orWhere('mobile', 'LIKE', "%{$term}%");
        });
    }

    /**
     * Scope to filter by department
     */
    public function scopeInDepartment(Builder $query, string $department): Builder
    {
        return $query->where('department', $department);
    }

    /**
     * Scope to filter by designation
     */
    public function scopeWithDesignation(Builder $query, string $designation): Builder
    {
        return $query->where('designation', $designation);
    }

    /**
     * Scope to filter by category
     */
    public function scopeOfCategory(Builder $query, string $category): Builder
    {
        return $query->where('category', $category);
    }

    /**
     * Scope to get employees who joined in a date range
     */
    public function scopeJoinedBetween(Builder $query, string $from, string $to): Builder
    {
        return $query->whereBetween('doj', [$from, $to]);
    }

    /**
     * Scope to order by name
     */
    public function scopeOrderByName(Builder $query, string $direction = 'asc'): Builder
    {
        return $query->orderBy('name', $direction);
    }

    // =========================================================================
    // HELPER METHODS
    // =========================================================================

    /**
     * Get all valid statuses
     */
    public static function getStatuses(): array
    {
        return [
            self::STATUS_ACTIVE,
            self::STATUS_INACTIVE,
            self::STATUS_ON_LEAVE,
            self::STATUS_TERMINATED,
        ];
    }

    /**
     * Get all valid categories
     */
    public static function getCategories(): array
    {
        return [
            self::CATEGORY_PERMANENT,
            self::CATEGORY_CONTRACT,
            self::CATEGORY_TRAINEE,
            self::CATEGORY_INTERN,
        ];
    }

    /**
     * Get all valid genders
     */
    public static function getGenders(): array
    {
        return [
            self::GENDER_MALE,
            self::GENDER_FEMALE,
            self::GENDER_OTHER,
        ];
    }

    /**
     * Check if employee is active
     */
    public function isActive(): bool
    {
        return $this->status === self::STATUS_ACTIVE;
    }

    /**
     * Get number of CL taken in a given year
     */
    public function clTakenThisYear($year = null): float
    {
        $year = $year ?: now()->year;
        return $this->leaves()
            ->where('leave_type', 'CL')
            ->whereYear('leave_from', $year)
            ->whereIn('status', ['Approved', 'Pending'])
            ->sum('number_of_days');
    }

    /**
     * Get CL accrued up to a given date (default: today)
     * Returns integer, e.g., Jan=1, Feb=2, ..., Dec=12
     */
    public function clAccrued(Carbon $asOf = null): int
    {
        $asOf = $asOf ?: now();
        return $asOf->month;
    }

    /**
     * Get available CL for this employee for a specific month/year
     */
    public function clAvailable($asOf = null): float
    {
        $asOf = $asOf ?: now();
        $year = $asOf->year;
        $accrued = $this->clAccrued($asOf);
        $taken = $this->leaves()
            ->where('leave_type', 'CL')
            ->whereYear('leave_from', $year)
            ->whereIn('status', ['Approved', 'Pending'])
            ->sum('number_of_days');
        return max(0, $accrued - $taken);
    }

    /**
     * Get employee's full name with ID
     */
    public function getFullIdentifierAttribute(): string
    {
        return "{$this->employee_id} - {$this->name}";
    }

    // =========================================================================
    // COMPUTED ATTRIBUTES
    // =========================================================================

    /**
     * Check availability for a given date (defaults to now)
     */
    public function getAvailabilityAttribute(): int
    {
        return $this->checkAvailability();
    }

    public function checkAvailability(?Carbon $referenceDate = null): int
    {
        $now = $referenceDate ?? Carbon::now();
        $startTime = $now->copy()->startOfDay();
        $endTime = $now->copy()->endOfDay();

        $inUse = $this->routeCardProcesses()
            ->where(function ($query) use ($startTime, $endTime) {
                $query->where(function ($q) use ($startTime, $endTime) {
                    $q->where('start_date', '<=', $startTime)
                      ->where('end_date', '>=', $endTime);
                })->orWhere(function ($q) use ($startTime) {
                    $q->where('start_date', '>=', $startTime);
                })->orWhere(function ($q) use ($startTime) {
                    $q->where('end_date', '<=', $startTime);
                });
            })
            ->exists();

        return $inUse ? 0 : 1;
    }

    /**
     * Get next available time for a given date (defaults to now)
     */
    public function getAvailableByAttribute(): string
    {
        return $this->getNextAvailability();
    }

    public function getNextAvailability(?Carbon $referenceDate = null): string
    {
        $now = $referenceDate ?? Carbon::now();
        $startTime = $now->copy()->startOfDay();
        $endTime = $now->copy()->endOfDay();

        $nextAvailability = $this->routeCardProcesses()
            ->where(function ($query) use ($startTime, $endTime) {
                $query->where(function ($q) use ($startTime, $endTime) {
                    $q->where('start_date', '<=', $startTime)
                      ->where('end_date', '>=', $endTime);
                })->orWhere(function ($q) use ($startTime) {
                    $q->where('start_date', '>=', $startTime);
                })->orWhere(function ($q) use ($startTime) {
                    $q->where('end_date', '<=', $startTime);
                });
            })
            ->orderBy('end_date', 'desc')
            ->value('end_date');

        return $nextAvailability
            ? Carbon::parse($nextAvailability)->format('Y-m-d H:i')
            : Carbon::parse($now)->format('Y-m-d H:i');
    }
}
