<?php
declare(strict_types=1);
namespace PpPhp\Generated\SocialAuth;

use PpPhp\Core\DB;
use PpPhp\Core\Events;

final class SocialAccountService
{
    public function __construct(private array $config) {}
    public function authenticate(array $identity): array
    {
        $this->ensureLinksTable(); $provider=(string)$identity['provider']; $subject=(string)$identity['subject'];
        $link=DB::FetchAll('SELECT user_id FROM social_auth_accounts WHERE provider = ? AND provider_subject = ? LIMIT 1',[$provider,$subject]);
        $user=$link ? $this->findById($link[0]['user_id']) : null;
        if (!$user && !empty($this->config['link_by_verified_email']) && !empty($identity['email_verified']) && $identity['email']!=='') $user=$this->findByEmail($identity['email']);
        if (!$user) {
            if (empty($this->config['auto_create_users'])) throw new \RuntimeException('No local account is linked. Ask an administrator to connect this identity.');
            $user=$this->createUser($identity);
        }
        $existing=DB::FetchAll('SELECT user_id FROM social_auth_accounts WHERE provider = ? AND provider_subject = ? LIMIT 1',[$provider,$subject]);
        if (!$existing) DB::Insert('social_auth_accounts',['user_id'=>$user['id'],'provider'=>$provider,'provider_subject'=>$subject,'email'=>$identity['email']?:null,'profile_json'=>json_encode($identity['raw']??[],JSON_UNESCAPED_SLASHES),'created_at'=>date('Y-m-d H:i:s'),'updated_at'=>date('Y-m-d H:i:s')]);
        $_SESSION['security_user']=['id'=>$user['id'],'username'=>$user['username'],'email'=>$user['email'],'role'=>$user['role'],'fullname'=>$user['fullname']?:$user['username']];
        Events::fire('global.auth.afterSocialLogin',['provider'=>$provider,'identity'=>$identity,'user'=>$_SESSION['security_user']]);
        return $_SESSION['security_user'];
    }
    private function map(): array { return ['table'=>$this->config['users_table']??'users','username'=>$this->config['username_field']??'username','email'=>$this->config['email_field']??'email','password'=>$this->config['password_field']??'password','fullname'=>$this->config['fullname_field']??'fullname','role'=>$this->config['role_field']??'role','status'=>$this->config['status_field']??'status']; }
    private function normalize(array $row): array { $m=$this->map(); return ['id'=>$row['id'],'username'=>(string)($row[$m['username']]??''),'email'=>(string)($row[$m['email']]??''),'fullname'=>(string)($row[$m['fullname']]??''),'role'=>(string)($row[$m['role']]??$this->config['default_role']??'registered')]; }
    private function findById(mixed $id): ?array { $m=$this->map(); $r=DB::Select($m['table'],['id'=>$id]); return $r?$this->normalize($r[0]):null; }
    private function findByEmail(string $email): ?array { $m=$this->map(); $r=DB::Select($m['table'],[$m['email']=>$email]); return $r?$this->normalize($r[0]):null; }
    private function createUser(array $i): array
    {
        $m=$this->map(); $email=(string)$i['email']; if ($email==='') throw new \RuntimeException('The provider did not supply an email address required to create an account.');
        if ($this->findByEmail($email)) throw new \RuntimeException('An account already uses this email. Sign in with its linked provider or ask an administrator to link it.');
        $base=preg_replace('/[^a-z0-9_]/','',strtolower(strtok($email,'@')?:'user'))?:'user'; $username=$base;
        for($n=0;DB::Select($m['table'],[$m['username']=>$username]);$n++) $username=$base.'_'.bin2hex(random_bytes(2));
        $data=[$m['username']=>$username,$m['email']=>$email,$m['password']=>password_hash(bin2hex(random_bytes(32)),PASSWORD_DEFAULT),$m['fullname']=>(string)($i['name']?:$username),$m['role']=>(string)($this->config['default_role']??'registered')];
        if ($m['status']!=='') $data[$m['status']]='active'; $id=DB::Insert($m['table'],$data); if (!$id) throw new \RuntimeException('Unable to create the local social account.');
        Events::fire('global.auth.afterSocialRegister',['provider'=>$i['provider'],'identity'=>$i,'user_id'=>$id]); return $this->findById($id)??throw new \RuntimeException('Created social account could not be loaded.');
    }
    private function ensureLinksTable(): void
    {
        $driver=DB::CurrentDriver();
        $sql=match($driver){'pgsql'=>'CREATE TABLE IF NOT EXISTS social_auth_accounts (id BIGSERIAL PRIMARY KEY,user_id BIGINT NOT NULL,provider VARCHAR(32) NOT NULL,provider_subject VARCHAR(255) NOT NULL,email VARCHAR(255),profile_json TEXT,created_at TIMESTAMP,updated_at TIMESTAMP,UNIQUE(provider,provider_subject))','sqlite'=>'CREATE TABLE IF NOT EXISTS social_auth_accounts (id INTEGER PRIMARY KEY AUTOINCREMENT,user_id INTEGER NOT NULL,provider TEXT NOT NULL,provider_subject TEXT NOT NULL,email TEXT,profile_json TEXT,created_at TEXT,updated_at TEXT,UNIQUE(provider,provider_subject))','sqlsrv'=>'IF OBJECT_ID(N\'social_auth_accounts\',N\'U\') IS NULL CREATE TABLE social_auth_accounts (id BIGINT IDENTITY(1,1) PRIMARY KEY,user_id BIGINT NOT NULL,provider NVARCHAR(32) NOT NULL,provider_subject NVARCHAR(255) NOT NULL,email NVARCHAR(255) NULL,profile_json NVARCHAR(MAX) NULL,created_at DATETIME2 NULL,updated_at DATETIME2 NULL,CONSTRAINT uq_social_provider_subject UNIQUE(provider,provider_subject))',default=>'CREATE TABLE IF NOT EXISTS social_auth_accounts (id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,user_id BIGINT UNSIGNED NOT NULL,provider VARCHAR(32) NOT NULL,provider_subject VARCHAR(255) NOT NULL,email VARCHAR(255) NULL,profile_json LONGTEXT NULL,created_at DATETIME NULL,updated_at DATETIME NULL,UNIQUE KEY uq_social_provider_subject (provider,provider_subject))'};
        DB::Exec($sql);
    }
}
