<?php

namespace App\Workflows\Services;

use App\Workflows\Models\StepInstance;
use App\Workflows\Models\WorkflowInstance;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Facades\DB;

/**
 * Persists step data to domain models using data bindings
 */
class StepDataPersister
{
    /**
     * Persist step data to domain models
     */
    public function persist(WorkflowInstance $instance, StepInstance $step, array $data): void
    {
        $template = $step->template;

        if (! $template || empty($template->data_bindings)) {
            return;
        }

        $subject = $instance->subject;
        $bindings = $template->data_bindings;

        DB::transaction(function () use ($subject, $bindings, $data) {
            foreach ($bindings as $field => $binding) {
                if (! isset($data[$field]) || ! isset($binding['target'])) {
                    continue;
                }

                $this->persistField($subject, $field, $data[$field], $binding);
            }

            // Call after_save hook if configured
            if (method_exists($subject, 'afterStepDataPersisted')) {
                $subject->afterStepDataPersisted($data);
            }
        });
    }

    /**
     * Persist a single field using its binding configuration
     */
    private function persistField(object $subject, string $field, mixed $value, array $binding): void
    {
        $target = $binding['target'];

        if (isset($target['relation'])) {
            // Update via relation
            $this->persistViaRelation($subject, $target['relation'], $field, $value, $binding);
        } elseif (isset($target['model']) && isset($target['unique_by'])) {
            // Update via direct model access
            $this->persistViaModel($subject, $target, $field, $value, $binding);
        }
    }

    /**
     * Persist via Eloquent relation
     */
    private function persistViaRelation(object $subject, string $relation, string $field, mixed $value, array $binding): void
    {
        $relationInstance = $subject->{$relation}();

        if ($relationInstance instanceof HasOne) {
            // Update or create via HasOne relation
            $relationInstance->updateOrCreate([], [$field => $value]);
        } elseif ($relationInstance instanceof BelongsTo) {
            // Update related model
            $related = $relationInstance->first();
            if ($related) {
                $related->update([$field => $value]);
            }
        }
    }

    /**
     * Persist via direct model access
     */
    private function persistViaModel(object $subject, array $target, string $field, mixed $value, array $binding): void
    {
        $modelClass = $target['model'];
        $uniqueBy = $target['unique_by'];

        // Build unique constraints
        $constraints = [];
        foreach ($uniqueBy as $constraint) {
            if ($constraint === 'subject_id') {
                $constraints[$constraint] = $subject->id;
            } elseif (isset($subject->{$constraint})) {
                $constraints[$constraint] = $subject->{$constraint};
            }
        }

        // Update or create
        $modelClass::updateOrCreate($constraints, [$field => $value]);
    }
}
