<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Schema;

final class AdminRbacSeeder extends Seeder
{
    /** @var array<string, array<int, string>> */
    private array $colsCache = [];

    public function run(): void
    {
        // ===== 0) 테이블 존재 체크 =====
        foreach (['admin_roles', 'admin_permissions', 'admin_permission_role', 'admin_users', 'admin_role_user'] as $t) {
            if (!Schema::hasTable($t)) {
                throw new \RuntimeException("Missing table: {$t}");
            }
        }

        // ===== 1) 역할(roles) =====
        // 현재 스키마가 name/label 기반(코드 없음)인 것에 맞춰 유지
        $roles = [
            ['name' => 'super_admin', 'label' => '최고관리자'],
            ['name' => 'finance',     'label' => '정산관리'],
            ['name' => 'product',     'label' => '상품관리'],
            ['name' => 'support',     'label' => 'CS/상담'],
        ];

        foreach ($roles as $r) {
            $payload = ['label' => $r['label']];
            $this->putTs($payload, 'admin_roles');

            DB::table('admin_roles')->updateOrInsert(
                ['name' => $r['name']],
                $payload
            );
        }

        // ===== 2) 권한(permissions) =====
        $perms = [
            ['name' => 'admin.access',        'label' => '관리자 접근'],
            ['name' => 'settlement.manage',   'label' => '정산 관리'],
            ['name' => 'product.manage',      'label' => '상품 관리'],
            ['name' => 'support.manage',      'label' => 'CS/상담 관리'],
            ['name' => 'member.manage',       'label' => '회원 관리'],
        ];

        foreach ($perms as $p) {
            $payload = ['label' => $p['label']];
            $this->putTs($payload, 'admin_permissions');

            DB::table('admin_permissions')->updateOrInsert(
                ['name' => $p['name']],
                $payload
            );
        }

        // ===== 3) 역할별 권한 매핑 =====
        // super_admin = 전체 권한
        // 일반 role = admin.access + 해당 도메인 권한
        $roleId = fn(string $name): int => (int) DB::table('admin_roles')->where('name', $name)->value('id');
        $permId = fn(string $name): int => (int) DB::table('admin_permissions')->where('name', $name)->value('id');

        $superRoleId = $roleId('super_admin');
        $financeRoleId = $roleId('finance');
        $productRoleId = $roleId('product');
        $supportRoleId = $roleId('support');

        $allPermIds = DB::table('admin_permissions')->pluck('id')->map(fn($v) => (int)$v)->all();
        foreach ($allPermIds as $pid) {
            $this->upsertPivot('admin_permission_role', [
                'admin_permission_id' => $pid,
                'admin_role_id'       => $superRoleId,
            ]);
        }

        // 일반 역할 권한
        $this->upsertPivot('admin_permission_role', [
            'admin_permission_id' => $permId('admin.access'),
            'admin_role_id'       => $financeRoleId,
        ]);
        $this->upsertPivot('admin_permission_role', [
            'admin_permission_id' => $permId('settlement.manage'),
            'admin_role_id'       => $financeRoleId,
        ]);

        $this->upsertPivot('admin_permission_role', [
            'admin_permission_id' => $permId('admin.access'),
            'admin_role_id'       => $productRoleId,
        ]);
        $this->upsertPivot('admin_permission_role', [
            'admin_permission_id' => $permId('product.manage'),
            'admin_role_id'       => $productRoleId,
        ]);

        $this->upsertPivot('admin_permission_role', [
            'admin_permission_id' => $permId('admin.access'),
            'admin_role_id'       => $supportRoleId,
        ]);
        $this->upsertPivot('admin_permission_role', [
            'admin_permission_id' => $permId('support.manage'),
            'admin_role_id'       => $supportRoleId,
        ]);

        // ===== 4) super_admin 유저 1명 보장(기존 로직 유지) =====
        $seedEmail = (string) env('ADMIN_SEED_EMAIL', 'sungro815@syye.net');
        $seedPw    = (string) env('ADMIN_SEED_PASSWORD', 'tjekdfl1324%^');
        $seedName  = (string) env('ADMIN_SEED_NAME', 'Super Admin');
        $seedPhone = (string) env('ADMIN_SEED_PHONE', '01036828958');

        $this->ensureUserWithRole(
            email: $seedEmail,
            passwordPlain: $seedPw,
            name: $seedName,
            nickname: '최고관리자',
            phoneRaw: $seedPhone,
            roleId: $superRoleId,
            // super_admin은 phone_hash를 써도 되지만, 스키마/정책 따라 충돌 피하려면 null도 OK
            usePhoneHash: true
        );

        // ===== 5) 일반 관리자 10명 생성 =====
        $fixedPw = 'tjekdfl1324%^';
        $fixedPhone = '01036828958';

        $koreanNames = [
            '김민준', '이서연', '박지훈', '최유진', '정현우',
            '한지민', '강다은', '조성훈', '윤하늘', '오지후',
        ];

        // 역할 배치: 1~3 finance / 4~6 product / 7~10 support
        for ($i = 1; $i <= 10; $i++) {
            $email = "admin{$i}@mail.com";
            $name  = $koreanNames[$i - 1];

            if ($i <= 3) {
                $rid = $financeRoleId;
                $nick = "정산담당{$i}";
            } elseif ($i <= 6) {
                $rid = $productRoleId;
                $nick = "상품담당".($i - 3);
            } else {
                $rid = $supportRoleId;
                $nick = "CS담당".($i - 6);
            }

            // 일반 관리자들은 동일 전화번호 사용 → phone_hash 충돌 방지 위해 usePhoneHash=false(=NULL 저장)
            $this->ensureUserWithRole(
                email: $email,
                passwordPlain: $fixedPw,
                name: $name,
                nickname: $nick,
                phoneRaw: $fixedPhone,
                roleId: $rid,
                usePhoneHash: false
            );
        }
    }

    /**
     * 유저 생성/갱신 + 역할 부여(스키마 컬럼/피벗 컬럼이 달라도 안전하게)
     */
    private function ensureUserWithRole(
        string $email,
        string $passwordPlain,
        string $name,
        string $nickname,
        string $phoneRaw,
        int $roleId,
        bool $usePhoneHash
    ): void {
        $hashKey = (string) config('admin.phone_hash_key', env('ADMIN_PHONE_HASH_KEY', ''));

        $phoneDigits = preg_replace('/\D+/', '', $phoneRaw) ?? '';
        $phoneEnc = $phoneDigits !== '' ? Crypt::encryptString($phoneDigits) : null;
        $last4 = $phoneDigits !== '' ? substr($phoneDigits, -4) : null;

        $hashKey = (string) config('admin.phone_hash_key', env('ADMIN_PHONE_HASH_KEY', ''));
        if ($hashKey === '') {
            throw new \RuntimeException('ADMIN_PHONE_HASH_KEY (admin.phone_hash_key) is empty. Set it in .env');
        }

        $phoneDigits = preg_replace('/\D+/', '', $phoneRaw) ?? '';
        if ($phoneDigits === '') {
            throw new \RuntimeException('Seed phone is empty');
        }

        /**
         * ✅ phone_hash는 NOT NULL이므로 항상 채운다.
         * - 운영 정책용(=진짜 조회키)일 때: phoneDigits만
         * - seed에서 같은 번호 10명 만들 때: phoneDigits + email로 유니크하게
         */
        $hashInput = $usePhoneHash
            ? $phoneDigits
            : ($phoneDigits . '|seed|' . $email);

        $phoneHash = hash_hmac('sha256', $hashInput, $hashKey);

        $exists = DB::table('admin_users')->where('email', $email)->first();

        if (!$exists) {
            $insert = [];

            $this->putIfCol($insert, 'admin_users', 'email', $email);
            $this->putIfCol($insert, 'admin_users', 'password', Hash::make($passwordPlain));

            $this->putIfCol($insert, 'admin_users', 'name', $name);
            $this->putIfCol($insert, 'admin_users', 'nickname', $nickname);

            $this->putIfCol($insert, 'admin_users', 'phone_enc', $phoneEnc);
            $this->putIfCol($insert, 'admin_users', 'phone_hash', $phoneHash); // 일반 관리자는 NULL
            $this->putIfCol($insert, 'admin_users', 'phone_last4', $last4);

            $this->putIfCol($insert, 'admin_users', 'status', 'active');
            $this->putIfCol($insert, 'admin_users', 'must_reset_password', 0);

            $this->putIfCol($insert, 'admin_users', 'two_factor_mode', 'sms');
            $this->putIfCol($insert, 'admin_users', 'totp_enabled', 0);

            $this->putIfCol($insert, 'admin_users', 'failed_login_count', 0);
            $this->putIfCol($insert, 'admin_users', 'locked_until', null);

            $this->putIfCol($insert, 'admin_users', 'remember_token', null);
            $this->putIfCol($insert, 'admin_users', 'deleted_at', null);

            $this->putTs($insert, 'admin_users');

            $adminUserId = DB::table('admin_users')->insertGetId($insert);
        } else {
            $adminUserId = (int) $exists->id;

            // 이미 있으면 갱신(비번/이름/닉/폰)
            $update = [];
            $this->putIfCol($update, 'admin_users', 'password', Hash::make($passwordPlain));
            $this->putIfCol($update, 'admin_users', 'name', $name);
            $this->putIfCol($update, 'admin_users', 'nickname', $nickname);
            $this->putIfCol($update, 'admin_users', 'phone_enc', $phoneEnc);
            $this->putIfCol($update, 'admin_users', 'phone_hash', $phoneHash);
            $this->putIfCol($update, 'admin_users', 'phone_last4', $last4);
            $this->putIfCol($update, 'admin_users', 'status', 'active');

            $this->putTs($update, 'admin_users', onlyUpdated: true);

            if (!empty($update)) {
                DB::table('admin_users')->where('id', $adminUserId)->update($update);
            }
        }

        // 역할 부여
        $this->upsertPivot('admin_role_user', [
            'admin_user_id' => $adminUserId,
            'admin_role_id' => $roleId,
        ]);
    }

    /**
     * Pivot upsert (assigned_at/created_at/updated_at 등 존재하면 자동 채움)
     */
    private function upsertPivot(string $table, array $keys): void
    {
        $values = [];

        // pivot에 assigned_at 같은 NOT NULL이 있을 수 있어 자동 세팅
        if ($this->hasCol($table, 'assigned_at')) $values['assigned_at'] = now();
        if ($this->hasCol($table, 'assigned_by')) $values['assigned_by'] = null;

        if ($this->hasCol($table, 'created_at')) $values['created_at'] = now();
        if ($this->hasCol($table, 'updated_at')) $values['updated_at'] = now();

        DB::table($table)->updateOrInsert($keys, $values);
    }

    private function putTs(array &$payload, string $table, bool $onlyUpdated = false): void
    {
        if ($this->hasCol($table, 'updated_at')) $payload['updated_at'] = now();
        if (!$onlyUpdated && $this->hasCol($table, 'created_at')) $payload['created_at'] = now();
    }

    private function putIfCol(array &$payload, string $table, string $col, mixed $value): void
    {
        if ($this->hasCol($table, $col)) {
            $payload[$col] = $value;
        }
    }

    private function hasCol(string $table, string $col): bool
    {
        if (!isset($this->colsCache[$table])) {
            $this->colsCache[$table] = Schema::getColumnListing($table);
        }
        return in_array($col, $this->colsCache[$table], true);
    }
}
