<?php

namespace App\Services\Dozn;

use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

final class DoznAccountAuthService
{
    private string $mode;
    private string $orgCode;
    private string $apiKey;
    private string $urlReal;
    private string $urlTest;
    private int $timeout;

    public function __construct()
    {
        $cfg = config('services.dozn', []);

        $this->mode    = (string)($cfg['mode'] ?? 'real');
        $this->orgCode = (string)($cfg['org_code'] ?? '');
        $this->apiKey  = (string)($cfg['api_key'] ?? '');
        $this->urlReal = (string)($cfg['url_real'] ?? '');
        $this->urlTest = (string)($cfg['url_test'] ?? '');
        $this->timeout = (int)($cfg['timeout'] ?? 10);
    }

    /**
     * 출금(out) 계좌 성명 인증 + 저장까지 한 번에 처리
     *
     * @param int    $memNo           회원번호
     * @param string $memberRealName  회원 실명(서버에서 꺼낸 값)
     * @param string $inputDepositor  입력한 예금주(화면 입력)
     * @param string $bankCode        은행코드(001~)
     * @param string $account         계좌번호(숫자만 권장)
     * @param bool   $requireSameName 입력 예금주 == 회원실명 강제 여부
     *
     * @return array { ok, code, message, status, depositor, telegram_no }
     */
    public function verifyAndSaveOutAccount(
        int $memNo,
        string $memberRealName,
        string $inputDepositor,
        string $bankCode,
        string $account,
        bool $requireSameName = true
    ): array {
        $memNo = (int)$memNo;
        $memberRealName = trim($memberRealName);
        $inputDepositor = trim($inputDepositor);
        $bankCode = trim($bankCode);
        $account = preg_replace('/[^0-9]/', '', (string)$account);

        // 은행코드 검증 (config/bank_code.php)
        $bankMap = (array) config('bank_code.flat', []);
        if ($bankCode === '' || !isset($bankMap[$bankCode])) {
            return $this->fail('INVALID_BANK', '은행코드가 올바르지 않습니다.');
        }
        if ($account === '') {
            return $this->fail('INVALID_ACCOUNT', '계좌번호를 입력해 주세요.');
        }
        if ($inputDepositor === '') {
            return $this->fail('INVALID_DEPOSITOR', '예금주를 입력해 주세요.');
        }

        // 입력 예금주와 회원 실명 일치 강제 (네 CI3 로직과 동일)
        if ($requireSameName && $memberRealName !== '' && $memberRealName !== $inputDepositor) {
            // act_state=4(정보불일치) 기록(선택)
            $this->upsertOutAccountState($memNo, $bankCode, $account, $inputDepositor, '4', [
                'reason' => 'member_name_mismatch',
                'member_real_name' => $memberRealName,
                'input_depositor' => $inputDepositor,
            ]);

            return $this->fail('NAME_MISMATCH', '가입자 성명과 예금주가 일치하지 않습니다!');
        }

        // telegram_no 발급 (일별 6자리)
        $telegramNo = $this->issueTelegramNo();

        // 요청 페이로드 (Dozn 스펙)
        $payload = [
            'org_code'        => $this->orgCode,
            'api_key'         => $this->apiKey,
            'telegram_no'     => $telegramNo,
            'bank_code'       => $bankCode,
            'account'         => $account,
            'check_depositor' => 'N', // 기존 코드 유지 (실명체크 identify_no는 추후)
            // 'identify_no'   => '761127',
        ];

        // 요청 로그 insert
        $logSeq = $this->logRequest($memNo, $payload, [
            'input_depositor' => $inputDepositor,
            'mode' => $this->mode,
        ]);

        // 진행상태 기록 (2: 인증진행중)
        $this->upsertOutAccountState($memNo, $bankCode, $account, $inputDepositor, '2', [
            'log_seq' => $logSeq,
            'telegram_no' => $telegramNo,
        ]);

        // Dozn 호출
        $resp = $this->callDozn($payload);

        // 결과 로그 update
        $this->logResult($logSeq, $resp['raw'] ?? []);

        $status = (string)($resp['status'] ?? '');
        $depositor = (string)($resp['depositor'] ?? '');

        // 성공(200) 처리
        if ($status === '200') {
            if ($depositor === '' || $depositor !== $inputDepositor) {
                // 정보 불일치 (4)
                $this->upsertOutAccountState($memNo, $bankCode, $account, $inputDepositor, '4', [
                    'status' => $status,
                    'returned_depositor' => $depositor,
                    'input_depositor' => $inputDepositor,
                    'telegram_no' => $telegramNo,
                ]);

                return [
                    'ok' => false,
                    'code' => 'DEPOSITOR_MISMATCH',
                    'message' => '예금주가 일치하지 않습니다!',
                    'status' => $status,
                    'depositor' => $depositor,
                    'telegram_no' => $telegramNo,
                ];
            }

            // 성공(3): 계좌 저장
            $this->saveOutAccountSuccess($memNo, $bankCode, $account, $depositor, [
                'status' => $status,
                'telegram_no' => $telegramNo,
                'result' => $resp['raw'] ?? [],
            ]);

            return [
                'ok' => true,
                'code' => 'OK',
                'message' => '인증완료 및 계좌번호가 등록되었습니다.',
                'status' => $status,
                'depositor' => $depositor,
                'telegram_no' => $telegramNo,
            ];
        }

        // 잘못된 계좌(520)
        if ($status === '520') {
            $this->upsertOutAccountState($memNo, $bankCode, $account, $inputDepositor, '5', [
                'status' => $status,
                'telegram_no' => $telegramNo,
                'result' => $resp['raw'] ?? [],
            ]);

            return [
                'ok' => false,
                'code' => 'INVALID_ACCOUNT',
                'message' => '잘 못된 계좌번호 입니다.',
                'status' => $status,
                'depositor' => $depositor,
                'telegram_no' => $telegramNo,
            ];
        }

        // 기타 오류
        $this->upsertOutAccountState($memNo, $bankCode, $account, $inputDepositor, '5', [
            'status' => $status,
            'telegram_no' => $telegramNo,
            'result' => $resp['raw'] ?? [],
        ]);

        return [
            'ok' => false,
            'code' => 'DOZN_ERROR',
            'message' => ($status ?: 'ERR') . '|인증에 문제가 발생했습니다. 잠시후 다시 시도해주세요.',
            'status' => $status,
            'depositor' => $depositor,
            'telegram_no' => $telegramNo,
        ];
    }

    // ------------------------------------------------------------------
    // Dozn HTTP call
    // ------------------------------------------------------------------
    private function callDozn(array $payload): array
    {
        $url = $this->mode === 'test' ? $this->urlTest : $this->urlReal;
        if ($url === '' || $this->orgCode === '' || $this->apiKey === '') {
            return [
                'status' => 'CONFIG',
                'depositor' => '',
                'raw' => ['error' => 'dozn_config_missing'],
            ];
        }

        try {
            $res = Http::timeout($this->timeout)
                ->acceptJson()
                ->asJson()
                ->post($url, $payload);

            $json = $res->json();
            if (!is_array($json)) $json = [];

            return [
                'status' => (string)($json['status'] ?? ''),
                'depositor' => (string)($json['depositor'] ?? ''),
                'raw' => $json,
            ];
        } catch (\Throwable $e) {
            Log::warning('[dozn] request failed', [
                'err' => $e->getMessage(),
            ]);

            return [
                'status' => 'HTTP',
                'depositor' => '',
                'raw' => ['error' => 'http_exception', 'message' => $e->getMessage()],
            ];
        }
    }

    // ------------------------------------------------------------------
    // telegram_no: mem_account_telegram_no 사용 (일별 6자리)
    // ------------------------------------------------------------------
    private function issueTelegramNo(): int
    {
        $today = Carbon::today()->toDateString();

        return (int) DB::transaction(function () use ($today) {
            // 오늘 발급된 마지막 telegram_no를 잠그고 다음 번호 생성
            $last = DB::table('mem_account_telegram_no')
                ->where('date', $today)
                ->lockForUpdate()
                ->max('telegram_no');

            $next = $last ? ((int)$last + 1) : 1;
            if ($next > 999999) $next = 1;

            DB::table('mem_account_telegram_no')->insert([
                'telegram_no' => $next,
                'date' => $today,
            ]);

            return $next;
        });
    }

    // ------------------------------------------------------------------
    // Logs: mem_account_log
    // ------------------------------------------------------------------
    private function logRequest(int $memNo, array $payload, array $meta = []): int
    {
        $now = Carbon::now()->format('Y-m-d H:i:s');

        $req = array_merge($payload, $meta);

        $reqJson = json_encode($req, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if ($reqJson === false) $reqJson = '{}';

        return (int) DB::table('mem_account_log')->insertGetId([
            'mem_no'       => $memNo,
            'request_data' => $reqJson,
            'request_time' => $now,
            'result_data'  => '{}',
            'result_time'  => $now,
        ]);
    }

    private function logResult(int $seq, array $result): void
    {
        if ($seq <= 0) return;

        $now = Carbon::now()->format('Y-m-d H:i:s');

        $resJson = json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if ($resJson === false) $resJson = '{}';

        DB::table('mem_account_log')
            ->where('seq', $seq)
            ->update([
                'result_data' => $resJson,
                'result_time' => $now,
            ]);
    }



    // ------------------------------------------------------------------
    // mem_account: 회원당 out 1개 유지 (스키마 유지, 코드로 강제)
    // ------------------------------------------------------------------
    private function upsertOutAccountState(
        int $memNo,
        string $bankCode,
        string $account,
        string $depositor,
        string $actState,
        array $confirmLog = []
    ): void {
        $bankMap = (array) config('bank_code.flat', []);
        $bankName = (string)($bankMap[$bankCode] ?? '');

        $now = Carbon::now()->format('Y-m-d H:i:s');

        DB::transaction(function () use ($memNo, $bankCode, $bankName, $account, $depositor, $actState, $confirmLog, $now) {
            // mem_no + act_type='out' 기준으로 row 잠금
            $row = DB::table('mem_account')
                ->where('mem_no', $memNo)
                ->where('act_type', 'out')
                ->lockForUpdate()
                ->first();

            $data = [
                'mem_no'        => $memNo,
                'act_type'      => 'out',
                'act_state'     => $actState,
                'bank_code'     => $bankCode,
                'bank_name'     => $bankName,
                'bank_act_name' => $depositor,
                'bank_act_num'  => $account,
                'in_date'       => $now,
                'act_date'      => $now,
                'confirm_log'   => json_encode($confirmLog ?: new \stdClass(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
            ];

            if ($row) {
                DB::table('mem_account')->where('seq', $row->seq)->update($data);
            } else {
                DB::table('mem_account')->insert($data);
            }
        });
    }

    private function saveOutAccountSuccess(
        int $memNo,
        string $bankCode,
        string $account,
        string $depositor,
        array $confirmLog = []
    ): void {
        // 성공은 act_state=3
        $this->upsertOutAccountState($memNo, $bankCode, $account, $depositor, '3', $confirmLog);
    }

    private function fail(string $code, string $message): array
    {
        return [
            'ok' => false,
            'code' => $code,
            'message' => $message,
        ];
    }
}
