<?php

namespace App\Services\FlowTester;

use Faker\Factory as FakerFactory;
use Faker\Generator as FakerGenerator;
use Illuminate\Support\Str;

/**
 * 변수 바인딩 엔진
 *
 * {{...}} 패턴을 감지하고 실제 값으로 치환합니다.
 * - {{variables.xxx}} - 전역 변수
 * - {{stepN.xxx}} - 이전 단계 추출값
 * - {{stepN.response.xxx}} - 이전 단계 전체 응답
 * - {{$timestamp}} - 현재 타임스탬프
 * - {{$uuid}} - 랜덤 UUID
 * - {{$random:N}} - N자리 랜덤 숫자
 * - {{$session.token}} - 세션에 저장된 Bearer 토큰
 * - {{$hmac.exp}} - HMAC 만료 시간 (현재 + 300초)
 * - {{$hmac.signature}} - HMAC-SHA256 서명
 * - {{$hmac.user_id}} - 현재 로그인 사용자 ID
 * - {{$hmac.tenant_id}} - 현재 테넌트 ID
 * - {{$faker.xxx}} - Faker 기반 랜덤 데이터 생성
 */
class VariableBinder
{
    private array $context = [];

    private ?FakerGenerator $faker = null;

    /**
     * 컨텍스트 초기화
     */
    public function reset(): void
    {
        $this->context = [];
    }

    /**
     * 전역 변수 설정
     * 환경변수 참조 ({{$env.XXX}})도 이 시점에 치환
     */
    public function setVariables(array $variables): void
    {
        // 환경변수 참조를 먼저 치환
        $resolvedVariables = [];
        foreach ($variables as $key => $value) {
            if (is_string($value)) {
                $resolvedVariables[$key] = $this->resolveBuiltins($value);
            } else {
                $resolvedVariables[$key] = $value;
            }
        }
        $this->context['variables'] = $resolvedVariables;
    }

    /**
     * 컨텍스트에 변수 추가
     */
    public function setVariable(string $key, mixed $value): void
    {
        data_set($this->context, $key, $value);
    }

    /**
     * 단계 결과 저장
     */
    public function setStepResult(string $stepId, array $extracted, array $fullResponse): void
    {
        $this->context['steps'][$stepId] = [
            'extracted' => $extracted,
            'response' => $fullResponse,
        ];

        // 편의를 위해 extracted 값을 stepId.key 형태로도 접근 가능하게
        foreach ($extracted as $key => $value) {
            $this->context['steps'][$stepId][$key] = $value;
        }
    }

    /**
     * 문자열/배열 내 모든 변수 치환
     */
    public function bind(mixed $input): mixed
    {
        if (is_string($input)) {
            return $this->bindString($input);
        }

        if (is_array($input)) {
            return array_map(fn ($v) => $this->bind($v), $input);
        }

        return $input;
    }

    /**
     * 문자열 내 변수 패턴 치환
     */
    private function bindString(string $input): string
    {
        // 내장 변수 처리
        $input = $this->resolveBuiltins($input);

        // 컨텍스트 변수 처리
        return preg_replace_callback(
            '/\{\{([^}]+)\}\}/',
            fn ($matches) => $this->resolveReference($matches[1]),
            $input
        );
    }

    /**
     * 내장 변수 처리 ($timestamp, $uuid, $random:N, $env.XXX, $auth.token)
     */
    private function resolveBuiltins(string $input): string
    {
        // {{$timestamp}} → 현재 타임스탬프
        $input = str_replace('{{$timestamp}}', (string) time(), $input);

        // {{$uuid}} → 랜덤 UUID
        $input = str_replace('{{$uuid}}', (string) Str::uuid(), $input);

        // {{$random:N}} → N자리 랜덤 숫자
        $input = preg_replace_callback(
            '/\{\{\$random:(\d+)\}\}/',
            function ($m) {
                $digits = (int) $m[1];
                $max = (int) pow(10, $digits) - 1;

                return str_pad((string) random_int(0, $max), $digits, '0', STR_PAD_LEFT);
            },
            $input
        );

        // {{$date}} → 현재 날짜 (Y-m-d)
        $input = str_replace('{{$date}}', date('Y-m-d'), $input);

        // {{$datetime}} → 현재 날짜시간 (Y-m-d H:i:s)
        $input = str_replace('{{$datetime}}', date('Y-m-d H:i:s'), $input);

        // {{$env.VAR_NAME}} → 환경변수에서 읽기
        $input = preg_replace_callback(
            '/\{\{\$env\.([A-Z_][A-Z0-9_]*)\}\}/i',
            fn ($m) => env($m[1], ''),
            $input
        );

        // {{$auth.token}} → 현재 로그인 사용자의 API 토큰
        if (str_contains($input, '{{$auth.token}}')) {
            $token = $this->getAuthToken();
            $input = str_replace('{{$auth.token}}', $token, $input);
        }

        // {{$auth.apiKey}} → .env의 API Key
        $input = str_replace('{{$auth.apiKey}}', env('FLOW_TESTER_API_KEY', ''), $input);

        // {{$session.xxx}} → 세션에 저장된 인증 정보 (API Explorer와 공유)
        $input = $this->resolveSessionVariables($input);

        // {{$hmac.xxx}} → HMAC 인증용 동적 값 생성
        $input = $this->resolveHmac($input);

        // {{$faker.xxx}} → Faker 기반 랜덤 데이터 생성
        $input = $this->resolveFaker($input);

        return $input;
    }

    /**
     * Faker 인스턴스 가져오기 (지연 초기화)
     */
    private function getFaker(): FakerGenerator
    {
        if ($this->faker === null) {
            $this->faker = FakerFactory::create('ko_KR');
        }

        return $this->faker;
    }

    /**
     * Faker 변수 처리
     *
     * 지원 패턴:
     * - {{$faker.name}} - 랜덤 이름
     * - {{$faker.company}} - 랜덤 회사명
     * - {{$faker.email}} - 랜덤 이메일
     * - {{$faker.phone}} - 랜덤 전화번호
     * - {{$faker.word}} - 랜덤 단어
     * - {{$faker.sentence}} - 랜덤 문장
     * - {{$faker.text}} - 랜덤 텍스트
     * - {{$faker.number:MIN:MAX}} - 범위 내 랜덤 정수
     * - {{$faker.price:MIN:MAX}} - 범위 내 랜덤 가격 (소수점 2자리)
     * - {{$faker.code:PREFIX:LENGTH}} - 코드 생성 (PREFIX + 숫자)
     * - {{$faker.itemCode:PREFIX}} - 품목코드 생성 (PREFIX + 6자리 숫자)
     * - {{$faker.address}} - 랜덤 주소
     * - {{$faker.url}} - 랜덤 URL
     * - {{$faker.boolean}} - true/false
     */
    private function resolveFaker(string $input): string
    {
        // {{$faker.xxx}} 또는 {{$faker.xxx:param1:param2}} 패턴 처리
        return preg_replace_callback(
            '/\{\{\$faker\.([a-zA-Z]+)(?::([^}]*))?\}\}/',
            fn ($m) => $this->generateFakerValue($m[1], $m[2] ?? ''),
            $input
        );
    }

    /**
     * Faker 값 생성
     */
    private function generateFakerValue(string $type, string $params): string
    {
        $faker = $this->getFaker();
        $paramList = $params !== '' ? explode(':', $params) : [];

        return match ($type) {
            // 기본 텍스트
            'name' => $faker->name(),
            'firstName' => $faker->firstName(),
            'lastName' => $faker->lastName(),
            'company' => $faker->company(),
            'email' => $faker->unique()->safeEmail(),
            'phone' => $faker->phoneNumber(),
            'word' => $faker->word(),
            'sentence' => $faker->sentence(),
            'text' => $faker->text(100),
            'paragraph' => $faker->paragraph(),

            // 주소
            'address' => $faker->address(),
            'city' => $faker->city(),
            'postcode' => $faker->postcode(),

            // 숫자
            'number' => $this->fakerNumber($faker, $paramList),
            'price' => $this->fakerPrice($faker, $paramList),
            'quantity' => (string) $faker->numberBetween(1, 100),

            // 코드 생성
            'code' => $this->fakerCode($paramList),
            'itemCode' => $this->fakerItemCode($paramList),
            'sku' => 'SKU-'.strtoupper($faker->bothify('??###')),
            'barcode' => $faker->ean13(),

            // 기타
            'url' => $faker->url(),
            'boolean' => $faker->boolean() ? 'true' : 'false',
            'uuid' => $faker->uuid(),
            'date' => $faker->date('Y-m-d'),
            'datetime' => $faker->dateTime()->format('Y-m-d H:i:s'),

            // 품목 관련
            'productName' => $this->fakerProductName($faker),
            'unit' => $faker->randomElement(['EA', 'SET', 'BOX', 'KG', 'M', 'L', 'PCS']),
            'category' => $faker->randomElement(['원자재', '부품', '반제품', '완제품', '소모품']),

            // HR 관련 (별칭 포함)
            'koreanName' => $faker->name(), // ko_KR locale이므로 한글 이름 생성
            'employeeCode' => $this->fakerEmployeeCode($paramList),
            'departmentName' => $faker->randomElement(['개발팀', '영업팀', '인사팀', '재무팀', '마케팅팀', '기획팀']),
            'positionName' => $faker->randomElement(['사원', '대리', '과장', '차장', '부장', '이사']),

            // 날짜 범위 지원
            'dateRange' => $this->fakerDateRange($faker, $paramList),

            default => "{{faker.{$type}}}",
        };
    }

    /**
     * 범위 내 랜덤 정수 생성
     */
    private function fakerNumber(FakerGenerator $faker, array $params): string
    {
        $min = isset($params[0]) ? (int) $params[0] : 1;
        $max = isset($params[1]) ? (int) $params[1] : 1000;

        return (string) $faker->numberBetween($min, $max);
    }

    /**
     * 범위 내 랜덤 가격 생성 (소수점 2자리)
     */
    private function fakerPrice(FakerGenerator $faker, array $params): string
    {
        $min = isset($params[0]) ? (float) $params[0] : 1000;
        $max = isset($params[1]) ? (float) $params[1] : 100000;

        return number_format($faker->randomFloat(2, $min, $max), 2, '.', '');
    }

    /**
     * 커스텀 코드 생성 (PREFIX + 숫자)
     */
    private function fakerCode(array $params): string
    {
        $prefix = $params[0] ?? 'CODE';
        $length = isset($params[1]) ? (int) $params[1] : 6;

        $digits = str_pad((string) random_int(0, (int) pow(10, $length) - 1), $length, '0', STR_PAD_LEFT);

        return $prefix.$digits;
    }

    /**
     * 품목코드 생성 (PREFIX + 6자리 숫자)
     */
    private function fakerItemCode(array $params): string
    {
        $prefix = $params[0] ?? 'ITEM';
        $digits = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);

        return $prefix.'-'.$digits;
    }

    /**
     * 제품명 생성 (한글)
     */
    private function fakerProductName(FakerGenerator $faker): string
    {
        $prefixes = ['고급', '프리미엄', '스탠다드', '베이직', '프로', '울트라'];
        $types = ['부품', '자재', '모듈', '키트', '세트', '패키지'];
        $models = ['A', 'B', 'C', 'X', 'Y', 'Z', 'Pro', 'Plus', 'Max'];

        return $faker->randomElement($prefixes).' '
            .$faker->randomElement($types).' '
            .$faker->randomElement($models).'-'
            .$faker->numberBetween(100, 999);
    }

    /**
     * 사원코드 생성 (PREFIX-XXXXXX 형식)
     *
     * 사용법: {{$faker.employeeCode}} → EMP-123456
     *        {{$faker.employeeCode:STAFF}} → STAFF-123456
     *        {{$faker.employeeCode:EMP:8}} → EMP-12345678
     */
    private function fakerEmployeeCode(array $params): string
    {
        $prefix = $params[0] ?? 'EMP';
        $length = isset($params[1]) ? (int) $params[1] : 6;

        $digits = str_pad((string) random_int(0, (int) pow(10, $length) - 1), $length, '0', STR_PAD_LEFT);

        return $prefix.'-'.$digits;
    }

    /**
     * 날짜 범위 내 랜덤 날짜 생성
     *
     * 사용법: {{$faker.dateRange:2024-01-01:2024-12-31}}
     *        {{$faker.dateRange:2024-01-01}} → 2024-01-01 ~ 오늘
     *        {{$faker.dateRange}} → 최근 1년 내
     */
    private function fakerDateRange(FakerGenerator $faker, array $params): string
    {
        $startDate = $params[0] ?? date('Y-m-d', strtotime('-1 year'));
        $endDate = $params[1] ?? date('Y-m-d');

        return $faker->dateTimeBetween($startDate, $endDate)->format('Y-m-d');
    }

    /**
     * 현재 로그인 사용자의 API 토큰 조회
     */
    private function getAuthToken(): string
    {
        $user = auth()->user();

        if (! $user) {
            return env('FLOW_TESTER_API_TOKEN', '');
        }

        // 사용자에게 저장된 API 토큰이 있으면 사용
        if (! empty($user->api_token)) {
            return $user->api_token;
        }

        // 없으면 .env의 기본 토큰 사용
        return env('FLOW_TESTER_API_TOKEN', '');
    }

    /**
     * HMAC 인증용 동적 값 생성
     *
     * 지원 패턴:
     * - {{$hmac.exp}} - 만료 시간 (현재 + 300초)
     * - {{$hmac.user_id}} - 선택된 사용자 ID (또는 현재 로그인 사용자)
     * - {{$hmac.tenant_id}} - 선택된 사용자의 테넌트 ID
     * - {{$hmac.signature}} - HMAC-SHA256 서명
     *
     * 서명 생성 방식:
     * - payload: "{user_id}:{tenant_id}:{exp}"
     * - signature: hash_hmac('sha256', payload, exchange_secret)
     *
     * 세션에서 선택된 사용자 정보를 우선 사용 (API Explorer와 공유)
     */
    private function resolveHmac(string $input): string
    {
        // HMAC 변수가 없으면 조기 반환
        if (! str_contains($input, '{{$hmac.')) {
            return $input;
        }

        // 세션에서 선택된 사용자 ID 가져오기 (API Explorer와 공유)
        $selectedUserId = session('api_explorer_user_id');

        // 선택된 사용자가 있으면 해당 사용자 정보 사용, 없으면 현재 로그인 사용자
        if ($selectedUserId) {
            $user = \App\Models\User::find($selectedUserId);
        } else {
            $user = auth()->user();
        }

        $userId = $user?->id ?? env('FLOW_TESTER_USER_ID', '');

        // tenant_id는 user_tenants 피벗 테이블에서 가져옴 (MNG users 테이블에는 tenant_id 컬럼 없음)
        $tenantId = env('FLOW_TESTER_TENANT_ID', '');
        if ($user) {
            $userTenant = \Illuminate\Support\Facades\DB::table('user_tenants')
                ->where('user_id', $user->id)
                ->where('is_default', true)
                ->whereNull('deleted_at')
                ->first();
            if ($userTenant) {
                $tenantId = $userTenant->tenant_id;
            }
        }

        // 만료 시간 (현재 + 300초)
        $exp = time() + 300;

        // 서명 생성
        $exchangeSecret = config('services.api.exchange_secret', '');
        $payload = "{$userId}:{$tenantId}:{$exp}";
        $signature = hash_hmac('sha256', $payload, $exchangeSecret);

        // 각 HMAC 변수 치환
        $input = str_replace('{{$hmac.exp}}', (string) $exp, $input);
        $input = str_replace('{{$hmac.user_id}}', (string) $userId, $input);
        $input = str_replace('{{$hmac.tenant_id}}', (string) $tenantId, $input);
        $input = str_replace('{{$hmac.signature}}', $signature, $input);

        return $input;
    }

    /**
     * 참조 경로 해석 (step1.pageId 또는 page_create_1.pageId → 실제 값)
     *
     * 지원 패턴:
     * - {{variables.xxx}} - 명시적 변수 참조
     * - {{xxx}} - 단축형 변수 참조 (variables.xxx로 폴백)
     * - {{stepId.xxx}} - 스텝 추출값 참조
     * - {{stepId.response.xxx}} - 스텝 전체 응답 참조
     */
    private function resolveReference(string $path): string
    {
        $path = trim($path);

        // variables.xxx → $this->context['variables']['xxx']
        if (str_starts_with($path, 'variables.')) {
            $value = data_get($this->context, $path, '');

            return $this->valueToString($value);
        }

        // stepId.xxx 패턴 감지 (stepId는 등록된 step의 ID)
        // 첫 번째 점(.) 기준으로 분리
        $dotPos = strpos($path, '.');
        if ($dotPos !== false) {
            $potentialStepId = substr($path, 0, $dotPos);
            $subPath = substr($path, $dotPos + 1);

            // 등록된 step인지 확인
            if (isset($this->context['steps'][$potentialStepId])) {
                // stepId.response.xxx → 전체 응답에서 추출
                if (str_starts_with($subPath, 'response.')) {
                    $responsePath = substr($subPath, 9); // "response." 제거
                    $value = data_get($this->context['steps'][$potentialStepId]['response'] ?? [], $responsePath, '');

                    return $this->valueToString($value);
                }

                // stepId.xxx → extracted 또는 직접 접근
                $value = data_get($this->context['steps'][$potentialStepId] ?? [], $subPath, '');

                return $this->valueToString($value);
            }
        }

        // 기타 경로는 context에서 직접 조회
        $value = data_get($this->context, $path, null);

        // 값이 없으면 variables.xxx로 폴백 시도 (단축형 지원)
        // 예: {{user_id}} → variables.user_id
        if ($value === null && ! str_contains($path, '.')) {
            $value = data_get($this->context, 'variables.'.$path, '');
        }

        return $this->valueToString($value ?? '');
    }

    /**
     * 값을 문자열로 변환 (배열/객체는 JSON)
     */
    private function valueToString(mixed $value): string
    {
        if (is_null($value)) {
            return '';
        }

        if (is_bool($value)) {
            return $value ? 'true' : 'false';
        }

        if (is_array($value) || is_object($value)) {
            return json_encode($value, JSON_UNESCAPED_UNICODE);
        }

        return (string) $value;
    }

    /**
     * 현재 컨텍스트 반환 (디버깅용)
     */
    public function getContext(): array
    {
        return $this->context;
    }

    /**
     * 세션 변수 처리
     *
     * 지원 패턴:
     * - {{$session.token}} - 세션에 저장된 Bearer 토큰
     * - {{$session.user_id}} - 세션 사용자 ID
     * - {{$session.user.email}} - 세션 사용자 이메일
     * - {{$session.user.name}} - 세션 사용자 이름
     * - {{$session.tenant_id}} - 세션 사용자의 기본 테넌트 ID
     */
    private function resolveSessionVariables(string $input): string
    {
        // 세션 변수가 없으면 조기 반환
        if (! str_contains($input, '{{$session.')) {
            return $input;
        }

        // {{$session.token}} → 세션에 저장된 Bearer 토큰
        if (str_contains($input, '{{$session.token}}')) {
            $token = session('api_explorer_token', '');
            $input = str_replace('{{$session.token}}', $token, $input);
        }

        // {{$session.user_id}} → 세션 사용자 ID
        if (str_contains($input, '{{$session.user_id}}')) {
            $userId = session('api_explorer_user_id', '');
            $input = str_replace('{{$session.user_id}}', (string) $userId, $input);
        }

        // 사용자 상세 정보가 필요한 경우 (user.email, user.name, tenant_id)
        $needsUserDetails = str_contains($input, '{{$session.user.') || str_contains($input, '{{$session.tenant_id}}');

        if ($needsUserDetails) {
            $userId = session('api_explorer_user_id');
            $user = $userId ? \App\Models\User::find($userId) : null;

            // {{$session.user.email}}
            if (str_contains($input, '{{$session.user.email}}')) {
                $email = $user?->email ?? '';
                $input = str_replace('{{$session.user.email}}', $email, $input);
            }

            // {{$session.user.name}}
            if (str_contains($input, '{{$session.user.name}}')) {
                $name = $user?->name ?? '';
                $input = str_replace('{{$session.user.name}}', $name, $input);
            }

            // {{$session.tenant_id}} → 사용자의 기본 테넌트 ID
            if (str_contains($input, '{{$session.tenant_id}}')) {
                $tenantId = '';
                if ($user) {
                    $userTenant = \Illuminate\Support\Facades\DB::table('user_tenants')
                        ->where('user_id', $user->id)
                        ->where('is_default', true)
                        ->whereNull('deleted_at')
                        ->first();
                    $tenantId = $userTenant?->tenant_id ?? '';
                }
                $input = str_replace('{{$session.tenant_id}}', (string) $tenantId, $input);
            }
        }

        return $input;
    }

    /**
     * 세션 인증 정보 조회 (FlowExecutor에서 사용)
     *
     * @return array{token: string, user_id: int|null, user: array|null, tenant_id: int|null}
     */
    public function getSessionAuth(): array
    {
        $token = session('api_explorer_token', '');
        $userId = session('api_explorer_user_id');
        $user = null;
        $tenantId = null;

        if ($userId) {
            $userModel = \App\Models\User::find($userId);
            if ($userModel) {
                $user = [
                    'id' => $userModel->id,
                    'name' => $userModel->name,
                    'email' => $userModel->email,
                ];

                // 기본 테넌트 조회
                $userTenant = \Illuminate\Support\Facades\DB::table('user_tenants')
                    ->where('user_id', $userModel->id)
                    ->where('is_default', true)
                    ->whereNull('deleted_at')
                    ->first();
                $tenantId = $userTenant?->tenant_id;
            }
        }

        return [
            'token' => $token,
            'user_id' => $userId,
            'user' => $user,
            'tenant_id' => $tenantId,
        ];
    }
}
