<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

use App\Traits\HotelScopeTrait;

class Staff extends Model
{
    use HasFactory, HotelScopeTrait;

    /**
     * Next sequential EMP#### ID for this hotel (based on existing EMP numbers).
     */
    public static function generateNextEmployeeId(?int $hotelId): string
    {
        $query = static::query();
        if ($hotelId) {
            $query->where('hotel_id', $hotelId);
        }

        $max = 1000;
        foreach ($query->pluck('employee_id') as $empId) {
            if ($empId && preg_match('/^EMP(\d+)$/i', (string) $empId, $m)) {
                $max = max($max, (int) $m[1]);
            }
        }

        return 'EMP' . ($max + 1);
    }

    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'staff';

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'hotel_id',
        // Identification
        'employee_id',
        'first_name',
        'last_name',
        'middle_name',
        'email',
        'phone',
        
        // Personal Information
        'date_of_birth',
        'gender',
        'nin', // National ID Number
        
        // Address Information
        'address',
        'residence',
        'city',
        'state',
        'zip_code',
        
        // Employment Information
        'position_id',
        'department_id',
        'hire_date',
        'status',
        'service_specialization',
        'max_assignments',
        'is_available',
        
        // Financial Information
        'salary',
        'tin_number', // Tax Identification Number
        'nssf_number', // Social Security Number
        'bank_name',
        'bank_account',
        
        // Emergency Contact
        'emergency_contact_name',
        'emergency_contact_phone',
        'emergency_contact_relationship',
        
        // Next of Kin
        'next_of_kin_name',
        'next_of_kin_relationship',
        'next_of_kin_phone',
        'next_of_kin_address',
        
        // Additional Information
        'profile_picture',
        'notes',
        'additional_notes',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'date_of_birth' => 'date',
        'hire_date' => 'date',
        'salary' => 'decimal:2',
        'is_available' => 'boolean',
        'max_assignments' => 'integer',
        'created_at' => 'datetime',
        'updated_at' => 'datetime',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'salary', // Hide salary from API responses if needed
    ];

    /**
     * The accessors to append to the model's array form.
     *
     * @var array<int, string>
     */
    protected $appends = [
        'full_name',
        'initials',
        'full_address',
        'profile_picture_url',
        'age',
        'years_of_service',
        'formatted_salary',
    ];

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

    /**
     * Get the position associated with the staff.
     */
    public function position()
    {
        return $this->belongsTo(StaffPosition::class, 'position_id');
    }

    /**
     * Get the department associated with the staff.
     */
    public function department()
    {
        return $this->belongsTo(StaffDepartment::class, 'department_id');
    }

    /**
     * Get the reservations assigned to this staff.
     */
    public function assignedReservations()
    {
        return $this->hasMany(Reservation::class, 'assigned_staff_id');
    }

    /**
     * Get the attendance records for this staff.
     */
    public function attendance()
    {
        return $this->hasMany(StaffAttendance::class, 'staff_id');
    }

    /**
     * Get the leave requests for this staff.
     */
    public function leaveRequests()
    {
        return $this->hasMany(StaffLeaveRequest::class, 'staff_id');
    }

    /**
     * Get the documents for this staff.
     */
    public function documents()
    {
        return $this->hasMany(StaffDocument::class, 'staff_id');
    }

    /**
     * Get the service requests assigned to this staff.
     */
    public function serviceRequests()
    {
        return $this->hasMany(ServiceRequest::class, 'assigned_staff_id');
    }

    /**
     * Get the active service assignments for this staff.
     */
    public function assignedServices()
    {
        return $this->hasMany(ServiceRequest::class, 'assigned_staff_id')
                    ->whereIn('status', ['pending', 'in_progress', 'assigned']);
    }

    /**
     * Get the completed services for this staff.
     */
    public function completedServices()
    {
        return $this->hasMany(ServiceRequest::class, 'assigned_staff_id')
                    ->where('status', 'completed');
    }

    /**
     * Get the service specializations for this staff.
     */
    public function serviceSpecializations()
    {
        return $this->belongsToMany(Service::class, 'staff_service_specializations')
                    ->withTimestamps();
    }

    /**
     * Get the service assignments for this staff.
     */
    public function serviceAssignments()
    {
        return $this->hasMany(ServiceAssignment::class, 'staff_id');
    }

    /**
     * Get the service ratings for this staff.
     */
    public function serviceRatings()
    {
        return $this->hasMany(ServiceRating::class, 'staff_id');
    }

    /**
     * Get the housekeeping tasks assigned to this staff.
     */
    public function housekeepingTasks()
    {
        return $this->hasMany(HousekeepingTask::class, 'assigned_staff_id');
    }

    // ==================== ACCESSORS ====================

    /**
     * Get the staff's full name.
     */
    public function getFullNameAttribute(): string
    {
        $nameParts = [$this->first_name];
        
        if ($this->middle_name) {
            $nameParts[] = $this->middle_name;
        }
        
        $nameParts[] = $this->last_name;
        
        return implode(' ', $nameParts);
    }

    /**
     * Get the staff's initials.
     */
    public function getInitialsAttribute(): string
    {
        $firstInitial = substr($this->first_name, 0, 1);
        $lastInitial = substr($this->last_name, 0, 1);
        
        return strtoupper($firstInitial . $lastInitial);
    }

    /**
     * Get the staff's full address.
     */
    public function getFullAddressAttribute(): string
    {
        $addressParts = [];
        
        if ($this->address) $addressParts[] = $this->address;
        if ($this->residence) $addressParts[] = $this->residence;
        if ($this->city) $addressParts[] = $this->city;
        if ($this->state) $addressParts[] = $this->state;
        if ($this->zip_code) $addressParts[] = $this->zip_code;
        
        return !empty($addressParts) ? implode(', ', $addressParts) : 'N/A';
    }

    /**
     * Get the profile picture URL.
     */
    public function getProfilePictureUrlAttribute(): ?string
    {
        if ($this->profile_picture) {
            return asset('storage/' . $this->profile_picture);
        }
        
        return null;
    }

    /**
     * Get the staff's age.
     */
    public function getAgeAttribute(): ?int
    {
        return $this->date_of_birth?->age;
    }

    /**
     * Get the staff's years of service.
     */
    public function getYearsOfServiceAttribute(): ?int
    {
        return $this->hire_date?->diffInYears(now());
    }

    /**
     * Get formatted salary.
     */
    public function getFormattedSalaryAttribute(): string
    {
        if (!$this->salary) {
            return 'N/A';
        }
        
        return 'UGX ' . number_format($this->salary, 2);
    }

    /**
     * Get current assignments count.
     */
    public function getCurrentAssignmentsCountAttribute(): int
    {
        return $this->assignedServices()->count();
    }

    /**
     * Check if staff is over-assigned.
     */
    public function getIsOverAssignedAttribute(): bool
    {
        if (!$this->max_assignments) {
            return false;
        }
        
        return $this->current_assignments_count >= $this->max_assignments;
    }

    /**
     * Get average rating.
     */
    public function getAverageRatingAttribute(): float
    {
        return $this->serviceRequests()
                    ->whereNotNull('rating')
                    ->avg('rating') ?? 0;
    }

    /**
     * Get formatted rating.
     */
    public function getFormattedRatingAttribute(): string
    {
        return number_format($this->average_rating, 1);
    }

    /**
     * Get emergency contact full details.
     */
    public function getEmergencyContactDetailsAttribute(): ?string
    {
        if (!$this->emergency_contact_name) {
            return null;
        }
        
        $details = $this->emergency_contact_name;
        
        if ($this->emergency_contact_relationship) {
            $details .= ' (' . $this->emergency_contact_relationship . ')';
        }
        
        if ($this->emergency_contact_phone) {
            $details .= ' - ' . $this->emergency_contact_phone;
        }
        
        return $details;
    }

    /**
     * Get next of kin full details.
     */
    public function getNextOfKinDetailsAttribute(): ?string
    {
        if (!$this->next_of_kin_name) {
            return null;
        }
        
        $details = $this->next_of_kin_name;
        
        if ($this->next_of_kin_relationship) {
            $details .= ' (' . $this->next_of_kin_relationship . ')';
        }
        
        if ($this->next_of_kin_phone) {
            $details .= ' - ' . $this->next_of_kin_phone;
        }
        
        return $details;
    }

    /**
     * Get bank details.
     */
    public function getBankDetailsAttribute(): ?string
    {
        if (!$this->bank_name || !$this->bank_account) {
            return null;
        }
        
        return $this->bank_name . ' - ' . $this->bank_account;
    }

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

    /**
     * Scope a query to only include active staff.
     */
    public function scopeActive($query)
    {
        return $query->where('status', 'active');
    }

    /**
     * Scope a query to only include inactive staff.
     */
    public function scopeInactive($query)
    {
        return $query->where('status', 'inactive');
    }

    /**
     * Scope a query to only include suspended staff.
     */
    public function scopeSuspended($query)
    {
        return $query->where('status', 'suspended');
    }

    /**
     * Scope a query to only include terminated staff.
     */
    public function scopeTerminated($query)
    {
        return $query->where('status', 'terminated');
    }

    /**
     * Scope a query to filter by department.
     */
    public function scopeByDepartment($query, $departmentId)
    {
        return $query->where('department_id', $departmentId);
    }

    /**
     * Scope a query to filter by position.
     */
    public function scopeByPosition($query, $positionId)
    {
        return $query->where('position_id', $positionId);
    }

    /**
     * Scope a query to only include available staff for service.
     */
    public function scopeAvailableForService($query)
    {
        return $query->where('is_available', true)
                    ->where('status', 'active');
    }

    /**
     * Scope a query to filter by service specialization.
     */
    public function scopeByServiceSpecialization($query, $serviceId)
    {
        return $query->whereHas('serviceSpecializations', function($q) use ($serviceId) {
            $q->where('service_id', $serviceId);
        });
    }

    /**
     * Scope a query to only include staff with low assignments.
     */
    public function scopeWithLowAssignments($query)
    {
        return $query->where(function($q) {
            $q->whereNull('max_assignments')
              ->orWhereRaw('(SELECT COUNT(*) FROM service_requests WHERE assigned_staff_id = staff.id AND status IN ("pending", "in_progress", "assigned")) < max_assignments');
        });
    }

    /**
     * Scope a query to only include service staff.
     */
    public function scopeServiceStaff($query)
    {
        return $query->whereHas('department', function($q) {
            $q->whereIn('name', ['Services', 'Housekeeping', 'Maintenance', 'Spa', 'Restaurant']);
        });
    }

    /**
     * Scope a query to only include housekeeping staff.
     */
    public function scopeHousekeeping($query)
    {
        return $query->whereHas('department', function($q) {
            $q->where('name', 'Housekeeping');
        });
    }

    /**
     * Scope a query to search staff.
     */
    public function scopeSearch($query, $search)
    {
        return $query->where(function($q) use ($search) {
            $q->where('first_name', 'like', "%{$search}%")
              ->orWhere('last_name', 'like', "%{$search}%")
              ->orWhere('middle_name', 'like', "%{$search}%")
              ->orWhere('email', 'like', "%{$search}%")
              ->orWhere('phone', 'like', "%{$search}%")
              ->orWhere('employee_id', 'like', "%{$search}%")
              ->orWhere('nin', 'like', "%{$search}%");
        });
    }

    // ==================== METHODS ====================

    /**
     * Check if staff can accept more assignments.
     */
    public function canAcceptMoreAssignments(): bool
    {
        if (!$this->max_assignments) {
            return true;
        }
        
        return $this->current_assignments_count < $this->max_assignments;
    }

    /**
     * Assign staff to a service request.
     */
    public function assignToService($serviceRequestId): bool
    {
        if (!$this->canAcceptMoreAssignments()) {
            return false;
        }

        $serviceRequest = ServiceRequest::find($serviceRequestId);
        
        if ($serviceRequest) {
            $serviceRequest->update([
                'assigned_staff_id' => $this->id,
                'status' => 'assigned'
            ]);
            
            return true;
        }

        return false;
    }

    /**
     * Complete a service request.
     */
    public function completeService($serviceRequestId): bool
    {
        $serviceRequest = $this->serviceRequests()->find($serviceRequestId);
        
        if ($serviceRequest) {
            $serviceRequest->update([
                'status' => 'completed',
                'completed_at' => now()
            ]);
            
            return true;
        }

        return false;
    }

    /**
     * Get service performance statistics.
     */
    public function getServicePerformanceStats(): array
    {
        $totalServices = $this->serviceRequests()->count();
        $completedServices = $this->completedServices()->count();
        $completionRate = $totalServices > 0 ? ($completedServices / $totalServices) * 100 : 0;

        return [
            'total_services' => $totalServices,
            'completed_services' => $completedServices,
            'completion_rate' => round($completionRate, 2),
            'average_rating' => $this->average_rating,
            'current_assignments' => $this->current_assignments_count,
            'max_assignments' => $this->max_assignments,
        ];
    }

    /**
     * Get housekeeping performance statistics.
     */
    public function getHousekeepingPerformanceStats(): array
    {
        $todayTasks = $this->housekeepingTasks()
            ->whereDate('scheduled_date', today())
            ->get();

        $totalTasks = $todayTasks->count();
        $completedTasks = $todayTasks->where('status', 'completed')->count();
        $completionRate = $totalTasks > 0 ? ($completedTasks / $totalTasks) * 100 : 0;

        return [
            'total_tasks' => $totalTasks,
            'completed_tasks' => $completedTasks,
            'completion_rate' => round($completionRate, 2),
            'pending_tasks' => $todayTasks->where('status', 'pending')->count(),
            'in_progress_tasks' => $todayTasks->where('status', 'in_progress')->count(),
        ];
    }

    /**
     * Update availability status.
     */
    public function updateAvailability(bool $available): self
    {
        $this->update(['is_available' => $available]);
        
        return $this;
    }

    /**
     * Add service specialization.
     */
    public function addServiceSpecialization($serviceId)
    {
        return $this->serviceSpecializations()->syncWithoutDetaching([$serviceId]);
    }

    /**
     * Remove service specialization.
     */
    public function removeServiceSpecialization($serviceId)
    {
        return $this->serviceSpecializations()->detach($serviceId);
    }

    /**
     * Check if staff is housekeeping staff.
     */
    public function isHousekeepingStaff(): bool
    {
        return $this->department && $this->department->name === 'Housekeeping';
    }

    /**
     * Toggle staff status.
     */
    public function toggleStatus(): self
    {
        $statuses = ['active', 'inactive', 'suspended', 'terminated'];
        $currentIndex = array_search($this->status, $statuses);
        $nextIndex = ($currentIndex + 1) % count($statuses);
        
        $this->update(['status' => $statuses[$nextIndex]]);
        
        return $this;
    }


    // ==================== EVENTS ====================

    /**
     * The "booted" method of the model.
     */
    protected static function booted()
    {
        static::creating(function ($staff) {
            if (!$staff->status) {
                $staff->status = 'active';
            }
            
            if (!isset($staff->is_available)) {
                $staff->is_available = true;
            }
        });

        static::updating(function ($staff) {
            // Log status changes if needed
            if ($staff->isDirty('status')) {
                \Log::info("Staff {$staff->id} status changed from {$staff->getOriginal('status')} to {$staff->status}");
            }
        });
    }
}