<?php

namespace App\Services\Danal;

use Illuminate\Support\Arr;
use Illuminate\Support\Str;

/**
 * Danal PASS(Authtel) 준비 서비스
 *
 * - prepare(): 다날 Start 페이지로 POST할 hidden fields를 구성
 * - callTrans / makeFormFields / getRandom 등은 별도 Client로 분리하는 게 이상적이지만,
 *   지금은 "작동 우선"으로 이 파일 하나로 정리할 수 있게 구성합니다.
 */
final class DanalAuthtelService
{
    private string $serviceUrl;
    private string $cpid;
    private string $cppwd;
    private string $charset;
    private int $connectTimeout;
    private int $timeout;
    private bool $debug;

    public function __construct()
    {
        $cfg = (array) config('danal.authtel', []);

        $this->serviceUrl      = (string) Arr::get($cfg, 'service_url', '');
        $this->cpid            = (string) Arr::get($cfg, 'cpid', '');
        $this->cppwd           = (string) Arr::get($cfg, 'cppwd', '');
        $this->charset         = (string) Arr::get($cfg, 'charset', 'UTF-8');
        $this->connectTimeout  = (int) Arr::get($cfg, 'connect_timeout', 5);
        $this->timeout         = (int) Arr::get($cfg, 'timeout', 30);
        $this->debug           = (bool) Arr::get($cfg, 'debug', false);
    }

    public function prepare(array $opt): array
    {
        // 필수 설정 체크
        if ($this->cpid === '' || $this->cppwd === '' || $this->serviceUrl === '') {
            return ['ok' => false, 'message' => 'DANAL 설정(CPID/CPPWD/SERVICE_URL)이 없습니다.'];
        }

        $targetUrl = (string) ($opt['targetUrl'] ?? '');
        $backUrl   = (string) ($opt['backUrl'] ?? '');

        if ($targetUrl === '') return ['ok' => false, 'message' => 'targetUrl 누락'];
        if ($backUrl === '')   return ['ok' => false, 'message' => 'backUrl 누락'];

        // 기본 타이틀
        $cpTitle = (string) ($opt['cpTitle'] ?? request()->getHost());

        // TransR
        $trans = [
            'TXTYPE'    => 'ITEMSEND',
            'SERVICE'   => 'UAS',
            'AUTHTYPE'  => '36',
            'CPID'      => $this->cpid,
            'CPPWD'     => $this->cppwd,
            'TARGETURL' => $targetUrl,
            'CPTITLE'   => $cpTitle,
        ];

        //  다날 서버 통신
        $res = $this->callTrans($trans);

        if (($res['RETURNCODE'] ?? '') !== '0000') {
            return [
                'ok' => false,
                'message' => $this->formatReturnMsg($res),
            ];
        }

        //  CI3의 ByPassValue
        $byPass = [
            // CI의 GetBgColor(0~10 => "00"~"10") 느낌 유지
            'BgColor'   => $this->getBgColor($this->getRandom(0, 10)),
            'BackURL'   => $backUrl,
            'IsCharSet' => $this->charset,
            'ByBuffer'  => 'This value bypass to CPCGI Page',
            'ByAnyName' => 'AnyValue',
        ];

        //  Start.php로 보낼 hidden fields 구성
        // - 여기서는 절대 e()/htmlspecialchars 같은 escape를 하지 마세요.
        // - 프론트에서 input.value로 넣으면 브라우저가 처리합니다.
        $fields = array_merge(
            $this->makeFormFields($res, ['RETURNCODE', 'RETURNMSG']),
            $this->makeFormFields($byPass)
        );

        return [
            'ok'     => true,
            'txid'   => $res['TID'] ?? $res['TXID'] ?? null,
            'fields' => $fields,
        ];
    }

    /**
     * 다날 서버 통신
     * @return array<string,string>
     */
    public function callTrans(array $reqData): array
    {
        // x-www-form-urlencoded
        $body = http_build_query($reqData, '', '&', PHP_QUERY_RFC3986);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_POST, 1);

        //  SSL 검증은 끄지 마세요 (운영 보안)
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);

        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectTimeout);
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
        curl_setopt($ch, CURLOPT_URL, $this->serviceUrl);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/x-www-form-urlencoded; charset=' . $this->charset,
        ]);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        $resStr = curl_exec($ch);

        if (($errno = curl_errno($ch)) !== 0) {
            $err = curl_error($ch) ?: 'curl error';
            curl_close($ch);

            return [
                'RETURNCODE' => '-1',
                'RETURNMSG'  => 'NETWORK ERROR(' . $errno . ':' . $err . ')',
            ];
        }

        curl_close($ch);

        // 다날 응답 파싱
        return $this->str2data((string) $resStr);
    }

    /** @return array<string,string> */
    private function str2data(string $str): array
    {
        $data = [];
        foreach (explode('&', $str) as $line) {
            $kv = explode('=', $line, 2);
            if (count($kv) === 2) {
                $data[$kv[0]] = $kv[1];
            }
        }
        return $data;
    }

    /**
     * hidden fields 만들기 (escape 하지 않음)
     * @param array<string, mixed> $arr
     * @param array<int, string> $excludeKeys
     * @return array<string, string>
     */
    private function makeFormFields(array $arr, array $excludeKeys = [], string $prefix = ''): array
    {
        $out = [];
        $preLen = strlen(trim($prefix));

        foreach ($arr as $key => $value) {
            $key = (string) $key;
            if ($key === '' || trim($key) === '') continue;

            if (in_array($key, $excludeKeys, true)) continue;

            if ($preLen > 0 && substr($key, 0, $preLen) !== $prefix) continue;

            // 다날은 value에 urlencoded가 들어갈 수 있음
            // 그대로 보내는 게 원칙이지만, 필요 시 여기서 정책적으로 urldecode/encode 조절 가능
            $out[$key] = is_scalar($value) || $value === null ? (string) $value : json_encode($value, JSON_UNESCAPED_UNICODE);
        }

        return $out;
    }

    private function getBgColor($bgColor): string
    {
        $color = 0;
        $i = (int) $bgColor;
        if ($i > 0 && $i < 11) $color = $i;
        return sprintf('%02d', $color);
    }

    private function getRandom(int $min, int $max): int
    {
        return random_int($min, $max);
    }

    /** @param array<string,string> $res */
    private function formatReturnMsg(array $res): string
    {
        $msg  = (string)($res['RETURNMSG'] ?? 'DANAL ERROR');
        $code = (string)($res['RETURNCODE'] ?? 'NO_CODE');
        return $msg . ' (' . $code . ')';
    }

    public function confirm(string $tid, int $confirmOption = 0, int $idenOption = 1): array
    {
        $req = [
            'TXTYPE'         => 'CONFIRM',
            'TID'            => $tid,
            'CONFIRMOPTION'  => $confirmOption,
            'IDENOPTION'     => $idenOption,
        ];

        // CI 주석: CONFIRMOPTION=1이면 CPID/ORDERID 필수
        if ($confirmOption === 1) {
            $req['CPID'] = $this->cpid;
        }

        return $this->callTrans($req);
    }


}
