<?php

namespace App\Services\ESign;

use App\Mail\EsignRequestMail;
use App\Models\ESign\EsignAuditLog;
use App\Models\ESign\EsignContract;
use App\Models\ESign\EsignSigner;
use App\Services\Service;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class EsignSignService extends Service
{
    public function __construct(
        private EsignAuditService $auditService,
        private EsignPdfService $pdfService,
    ) {}

    public function getByToken(string $token): array
    {
        $signer = EsignSigner::withoutGlobalScopes()
            ->where('access_token', $token)
            ->first();

        if (! $signer) {
            throw new NotFoundHttpException(__('error.esign.invalid_token'));
        }

        if ($signer->token_expires_at && $signer->token_expires_at->isPast()) {
            throw new BadRequestHttpException(__('error.esign.token_expired'));
        }

        $contract = EsignContract::withoutGlobalScopes()
            ->with(['signers:id,contract_id,name,role,status,signed_at'])
            ->find($signer->contract_id);

        if (! $contract) {
            throw new NotFoundHttpException(__('error.not_found'));
        }

        $this->auditService->logPublic(
            $contract->tenant_id,
            $contract->id,
            EsignAuditLog::ACTION_VIEWED,
            $signer->id
        );

        return [
            'contract' => $contract,
            'signer' => $signer,
        ];
    }

    public function sendOtp(string $token): array
    {
        $signer = $this->findSignerByToken($token);
        $contract = EsignContract::withoutGlobalScopes()->find($signer->contract_id);

        if (! $contract || ! $contract->canSign()) {
            throw new BadRequestHttpException(__('error.esign.contract_not_signable'));
        }

        // OTP 생성 (6자리)
        $otpCode = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);

        $signer->update([
            'otp_code' => $otpCode,
            'otp_expires_at' => now()->addMinutes(5),
            'otp_attempts' => 0,
        ]);

        // OTP 이메일 발송 (실제 발송은 추후 Mail 클래스에서)
        // 현재는 개발 편의를 위해 로그에만 기록
        \Illuminate\Support\Facades\Log::info("E-Sign OTP: {$otpCode} for {$signer->email}");

        $this->auditService->logPublic(
            $contract->tenant_id,
            $contract->id,
            EsignAuditLog::ACTION_OTP_SENT,
            $signer->id,
            ['email' => $signer->email]
        );

        return ['message' => __('message.esign.otp_sent')];
    }

    public function verifyOtp(string $token, string $otpCode): array
    {
        $signer = $this->findSignerByToken($token);

        if ($signer->otp_attempts >= 5) {
            throw new BadRequestHttpException(__('error.esign.otp_max_attempts'));
        }

        if (! $signer->otp_code || ! $signer->otp_expires_at) {
            throw new BadRequestHttpException(__('error.esign.otp_not_sent'));
        }

        if ($signer->otp_expires_at->isPast()) {
            throw new BadRequestHttpException(__('error.esign.otp_expired'));
        }

        $signer->increment('otp_attempts');

        if ($signer->otp_code !== $otpCode) {
            throw new BadRequestHttpException(__('error.esign.otp_invalid'));
        }

        $contract = EsignContract::withoutGlobalScopes()->find($signer->contract_id);

        $signer->update([
            'auth_verified_at' => now(),
            'otp_code' => null,
            'otp_expires_at' => null,
            'status' => EsignSigner::STATUS_AUTHENTICATED,
        ]);

        $this->auditService->logPublic(
            $contract->tenant_id,
            $contract->id,
            EsignAuditLog::ACTION_AUTHENTICATED,
            $signer->id
        );

        // sign_session_token 발급 (JWT 대신 단순 토큰)
        $sessionToken = Str::random(64);

        return [
            'sign_session_token' => $sessionToken,
            'signer' => $signer->fresh(),
        ];
    }

    public function submitSignature(string $token, array $data): EsignSigner
    {
        $signer = $this->findSignerByToken($token);

        if (! $signer->isVerified()) {
            throw new BadRequestHttpException(__('error.esign.not_verified'));
        }

        if ($signer->hasSigned()) {
            throw new BadRequestHttpException(__('error.esign.already_signed'));
        }

        $contract = EsignContract::withoutGlobalScopes()->find($signer->contract_id);

        if (! $contract || ! $contract->canSign()) {
            throw new BadRequestHttpException(__('error.esign.contract_not_signable'));
        }

        return DB::transaction(function () use ($signer, $contract, $data) {
            // 서명 이미지 저장
            $signatureImagePath = null;
            if (! empty($data['signature_image'])) {
                $imageData = base64_decode($data['signature_image']);
                $signatureImagePath = "esign/{$contract->tenant_id}/signatures/{$contract->id}_{$signer->id}.png";
                \Illuminate\Support\Facades\Storage::disk('local')->put($signatureImagePath, $imageData);
            }

            $request = request();
            $signer->update([
                'signature_image_path' => $signatureImagePath,
                'signed_at' => now(),
                'consent_agreed_at' => now(),
                'sign_ip_address' => $request->ip(),
                'sign_user_agent' => mb_substr($request->userAgent() ?? '', 0, 500),
                'status' => EsignSigner::STATUS_SIGNED,
            ]);

            $this->auditService->logPublic(
                $contract->tenant_id,
                $contract->id,
                EsignAuditLog::ACTION_SIGNED,
                $signer->id,
                ['ip' => $request->ip()]
            );

            // 완료 여부 확인 및 처리
            $this->checkAndComplete($contract);

            return $signer->fresh();
        });
    }

    public function reject(string $token, string $reason): EsignSigner
    {
        $signer = $this->findSignerByToken($token);

        if ($signer->hasSigned()) {
            throw new BadRequestHttpException(__('error.esign.already_signed'));
        }

        $contract = EsignContract::withoutGlobalScopes()->find($signer->contract_id);

        return DB::transaction(function () use ($signer, $contract, $reason) {
            $signer->update([
                'status' => EsignSigner::STATUS_REJECTED,
                'rejected_reason' => $reason,
            ]);

            $contract->update([
                'status' => EsignContract::STATUS_REJECTED,
            ]);

            $this->auditService->logPublic(
                $contract->tenant_id,
                $contract->id,
                EsignAuditLog::ACTION_REJECTED,
                $signer->id,
                ['reason' => $reason]
            );

            return $signer->fresh();
        });
    }

    private function checkAndComplete(EsignContract $contract): void
    {
        $allSigned = $contract->signers()
            ->where('status', '!=', EsignSigner::STATUS_SIGNED)
            ->doesntExist();

        if ($allSigned) {
            // 모든 서명 완료
            $contract->update([
                'status' => EsignContract::STATUS_COMPLETED,
                'completed_at' => now(),
            ]);

            $this->auditService->logPublic(
                $contract->tenant_id,
                $contract->id,
                EsignAuditLog::ACTION_COMPLETED
            );
        } else {
            // 부분 서명 상태 업데이트
            $signedCount = $contract->signers()->where('status', EsignSigner::STATUS_SIGNED)->count();
            if ($signedCount > 0 && $contract->status === EsignContract::STATUS_PENDING) {
                $contract->update(['status' => EsignContract::STATUS_PARTIALLY_SIGNED]);
            }

            // 다음 서명자에게 알림
            $nextSigner = $contract->getNextSigner();
            if ($nextSigner && $nextSigner->status === EsignSigner::STATUS_WAITING) {
                $nextSigner->update(['status' => EsignSigner::STATUS_NOTIFIED]);
                Mail::to($nextSigner->email)->queue(
                    new EsignRequestMail($contract, $nextSigner)
                );
            }
        }
    }

    private function findSignerByToken(string $token): EsignSigner
    {
        $signer = EsignSigner::withoutGlobalScopes()
            ->where('access_token', $token)
            ->first();

        if (! $signer) {
            throw new NotFoundHttpException(__('error.esign.invalid_token'));
        }

        if ($signer->token_expires_at && $signer->token_expires_at->isPast()) {
            throw new BadRequestHttpException(__('error.esign.token_expired'));
        }

        return $signer;
    }
}
