<?php

namespace App\Traits;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;

/**
 * SoftDeleteResolution Trait
 *
 * This trait provides functionality for handling conflicts with soft-deleted records
 * that violate unique constraints when creating or updating models.
 *
 * Usage:
 * 1. Add this trait to any model that uses SoftDeletes
 * 2. Define uniqueFields() method in your model to specify which fields should be considered unique
 * 3. Use findSoftDeletedDuplicate() to detect conflicts before saving
 * 4. Use restoreSoftDeletedRecord() to restore a soft-deleted record with updated attributes
 *
 * Example:
 * ```
 * class User extends Model
 * {
 *     use SoftDeletes, SoftDeleteResolution;
 *
 *     // Define which fields should be considered for uniqueness checks
 *     protected function uniqueFields(): array
 *     {
 *         return ['email'];
 *     }
 * }
 *
 * // In your controller:
 * $newUser = new User(['name' => 'John', 'email' => 'john@example.com']);
 *
 * // Check for soft-deleted duplicate
 * $duplicate = $newUser->findSoftDeletedDuplicate();
 *
 * if ($duplicate) {
 *     // Restore with updated attributes
 *     $restored = $newUser->restoreSoftDeletedRecord($duplicate, ['name' => 'John']);
 *     return "Restored previously deleted user with updated details.";
 * }
 *
 * // Otherwise, proceed with normal save
 * $newUser->save();
 * ```
 */
trait SoftDeleteResolution
{
    /**
     * Check if the current model has the SoftDeletes trait
     *
     * @return bool
     */
    protected function usesSoftDeletes(): bool
    {
        return in_array(SoftDeletes::class, class_uses_recursive($this));
    }

    /**
     * Get fields that should be considered unique for this model
     * This method should be implemented in the model using this trait
     *
     * @return array An array of field names that should be unique
     */
    protected function uniqueFields(): array
    {
        // Default implementation returns empty array
        // Models should override this method to specify their unique fields
        return [];
    }

    /**
     * Check if there's a soft-deleted record that would conflict with this one
     *
     * @param array $attributes Optional custom attributes to check instead of the model's current attributes
     * @return Model|null The conflicting soft-deleted model, or null if none exists
     */
    public function findSoftDeletedDuplicate(array $attributes = []): ?Model
    {
        // Ensure the model uses soft deletes
        if (!$this->usesSoftDeletes()) {
            Log::warning('SoftDeleteResolution trait used on model without SoftDeletes', [
                'model' => get_class($this)
            ]);
            return null;
        }

        // Get unique fields for this model
        $uniqueFields = $this->uniqueFields();

        if (empty($uniqueFields)) {
            Log::warning('No unique fields defined for model using SoftDeleteResolution', [
                'model' => get_class($this)
            ]);
            return null;
        }

        try {
            // Use provided attributes or current model attributes
            $checkAttributes = empty($attributes) ? $this->getAttributes() : $attributes;

            // Build query to find soft-deleted duplicates
            $query = static::onlyTrashed();

            foreach ($uniqueFields as $field) {
                // Only include fields that exist in checkAttributes and have a value
                if (array_key_exists($field, $checkAttributes) && !is_null($checkAttributes[$field])) {
                    $query->where($field, $checkAttributes[$field]);
                }
            }

            // Return the first matching soft-deleted record
            return $query->first();
        } catch (\Exception $e) {
            Log::error('Error checking for soft-deleted duplicates', [
                'model' => get_class($this),
                'error' => $e->getMessage(),
                'attributes' => $attributes ?: $this->getAttributes()
            ]);

            return null;
        }
    }

    /**
     * Restore a soft-deleted record and optionally update its attributes
     *
     * @param Model $model The soft-deleted model to restore
     * @param array $attributes New attributes to apply during restoration
     * @return Model|null The restored model, or null if restoration failed
     */
    public function restoreSoftDeletedRecord(Model $model, array $attributes = []): ?Model
    {
        // Ensure the model is actually soft-deleted
        if (!method_exists($model, 'restore') || is_null($model->deleted_at)) {
            Log::warning('Attempted to restore a model that is not soft-deleted', [
                'model' => get_class($model),
                'id' => $model->getKey()
            ]);
            return null;
        }

        try {
            // Begin transaction to ensure atomicity
            DB::beginTransaction();

            // Apply any new attributes
            if (!empty($attributes)) {
                $model->fill($attributes);
            }

            // Restore the model (removes deleted_at timestamp)
            $model->restore();

            // Save any updated attributes
            if (!empty($attributes)) {
                $model->save();
            }

            // Commit transaction
            DB::commit();

            Log::info('Successfully restored soft-deleted record', [
                'model' => get_class($model),
                'id' => $model->getKey(),
                'attributes_updated' => !empty($attributes)
            ]);

            return $model;
        } catch (\Exception $e) {
            // Rollback transaction on error
            DB::rollBack();

            Log::error('Failed to restore soft-deleted record', [
                'model' => get_class($model),
                'id' => $model->getKey(),
                'error' => $e->getMessage(),
                'attributes' => $attributes
            ]);

            return null;
        }
    }

    /**
     * Detect if a QueryException is due to a unique constraint violation on a soft-deleted record
     *
     * @param QueryException $exception The exception to check
     * @return bool True if the exception is caused by a conflict with a soft-deleted record
     */
    public function isSoftDeletedUniqueConstraintViolation(QueryException $exception): bool
    {
        // Check if the model uses soft deletes
        if (!$this->usesSoftDeletes()) {
            return false;
        }

        // Check if the exception is due to a unique constraint violation
        // PostgreSQL error code 23505 is for unique_violation
        $isPgsqlUniqueViolation = $exception->getCode() === '23505';

        // SQLite and MySQL use different error codes
        $isMysqlUniqueViolation = strpos($exception->getMessage(), 'Duplicate entry') !== false;
        $isSqliteUniqueViolation = strpos($exception->getMessage(), 'UNIQUE constraint failed') !== false;

        // If it's not a unique constraint violation, return false
        if (!($isPgsqlUniqueViolation || $isMysqlUniqueViolation || $isSqliteUniqueViolation)) {
            return false;
        }

        // At this point, we have a unique constraint violation
        // Now we need to check if the conflicting record is soft-deleted

        $uniqueFields = $this->uniqueFields();
        if (empty($uniqueFields)) {
            return false;
        }

        // Check if there's a soft-deleted record with the same unique values
        return $this->findSoftDeletedDuplicate() !== null;
    }

    /**
     * Attempt to resolve a unique constraint violation by restoring a soft-deleted record
     *
     * @param array $attributes Attributes for the new or updated record
     * @return Model|null The restored model, or null if no resolution was possible
     */
    public function resolveSoftDeletedConflict(array $attributes = []): ?Model
    {
        $duplicate = $this->findSoftDeletedDuplicate($attributes);

        if ($duplicate) {
            return $this->restoreSoftDeletedRecord($duplicate, $attributes);
        }

        return null;
    }
}
