<?php
declare(strict_types=1);
namespace PpPhp\Core\Projects;

final class ProjectGitService
{
    private const IGNORE=["/config/database.php","/database/","/.restore-*","/.checkpoint-*",".DS_Store"];
    public function status(string $dir):array
    {
        $available=$this->run(['git','--version'],$dir,false)['code']===0;if(!$available)return ['available'=>false,'initialized'=>false,'message'=>'Git is not installed on this server.'];$initialized=is_dir($dir.'/.git');if(!$initialized)return ['available'=>true,'initialized'=>false,'branch'=>'','dirty_count'=>0,'changes'=>[],'last_commit'=>null,'remote'=>'','sensitive_tracked'=>false];
        $status=$this->run(['git','status','--porcelain=v1'],$dir);$lines=array_values(array_filter(explode("\n",trim($status['output']))));$branch=trim($this->run(['git','branch','--show-current'],$dir,false)['output']);$last=$this->run(['git','log','-1','--pretty=format:%H%x1f%h%x1f%s%x1f%aI'],$dir,false)['output'];$parts=explode("\x1f",$last);$remote=trim($this->run(['git','remote','get-url','origin'],$dir,false)['output']);$tracked=$this->run(['git','ls-files','--error-unmatch','config/database.php'],$dir,false)['code']===0;
        return ['available'=>true,'initialized'=>true,'branch'=>$branch,'dirty_count'=>count($lines),'changes'=>array_slice($lines,0,100),'last_commit'=>count($parts)>=4?['hash'=>$parts[0],'short'=>$parts[1],'subject'=>$parts[2],'date'=>$parts[3]]:null,'remote'=>$remote,'sensitive_tracked'=>$tracked];
    }
    public function initialize(string $dir,array $settings):array
    {
        $this->writeIgnore($dir);if(!is_dir($dir.'/.git')){$result=$this->run(['git','init','-b',(string)($settings['default_branch']??'main')],$dir,false);if($result['code']!==0){$this->run(['git','init'],$dir);$this->run(['git','branch','-M',(string)($settings['default_branch']??'main')],$dir,false);}}
        $this->configure($dir,$settings);$this->run(['git','add','-A','--','.'],$dir);$commit=$this->run(['git','commit','-m','Initial PPphp project checkpoint'],$dir,false);return ['status'=>$this->status($dir),'message'=>$commit['code']===0?'Git initialized with the first commit.':'Git initialized. There were no new files to commit.'];
    }
    public function commit(string $dir,string $message,array $settings):array
    {
        $message=trim($message);if($message===''||mb_strlen($message)>200)throw new \InvalidArgumentException('Commit message is required and must not exceed 200 characters.');if(!is_dir($dir.'/.git'))throw new \RuntimeException('Initialize Git before creating a commit.');$this->writeIgnore($dir);$this->configure($dir,$settings);$this->run(['git','add','-A','--','.'],$dir);$result=$this->run(['git','commit','-m',$message],$dir,false);if($result['code']!==0&&!str_contains(strtolower($result['combined']),'nothing to commit'))throw new \RuntimeException('Git commit failed: '.$result['combined']);return ['status'=>$this->status($dir),'message'=>$result['code']===0?'Git commit created.':'Nothing changed since the last commit.'];
    }
    public function applySettings(string $dir,array $settings):array
    {
        if(!is_dir($dir.'/.git'))return $this->status($dir);$this->writeIgnore($dir);$this->configure($dir,$settings);$branch=trim($this->run(['git','branch','--show-current'],$dir,false)['output']);$target=(string)($settings['default_branch']??'main');if($branch!==''&&$branch!==$target)$this->run(['git','branch','-M',$target],$dir);return $this->status($dir);
    }
    public function validateSettings(array $input):array
    {
        $branch=trim((string)($input['default_branch']??'main'));if(!preg_match('/^[A-Za-z0-9][A-Za-z0-9._\/-]{0,99}$/',$branch)||str_contains($branch,'..'))throw new \InvalidArgumentException('Invalid Git branch name.');$email=trim((string)($input['author_email']??''));if($email!==''&&!filter_var($email,FILTER_VALIDATE_EMAIL))throw new \InvalidArgumentException('Enter a valid Git author email.');$remote=trim((string)($input['remote_url']??''));if($remote!==''&&!$this->validRemote($remote))throw new \InvalidArgumentException('Use an HTTPS, SSH, or git@host repository URL without embedded credentials.');return ['remote_url'=>$remote,'default_branch'=>$branch,'author_name'=>mb_substr(trim((string)($input['author_name']??'')),0,120),'author_email'=>$email,'auto_checkpoint'=>!empty($input['auto_checkpoint'])];
    }
    private function configure(string $dir,array $s):void{if(!empty($s['author_name']))$this->run(['git','config','user.name',(string)$s['author_name']],$dir);if(!empty($s['author_email']))$this->run(['git','config','user.email',(string)$s['author_email']],$dir);if(!empty($s['remote_url'])){$has=$this->run(['git','remote','get-url','origin'],$dir,false)['code']===0;$this->run(['git','remote',$has?'set-url':'add','origin',(string)$s['remote_url']],$dir);}}
    private function writeIgnore(string $dir):void{$path=rtrim($dir,'/').'/.gitignore';$existing=is_file($path)?file($path,FILE_IGNORE_NEW_LINES):[];$lines=array_values(array_unique(array_merge(is_array($existing)?$existing:[],self::IGNORE)));if(file_put_contents($path,implode("\n",$lines)."\n",LOCK_EX)===false)throw new \RuntimeException('Could not write the project .gitignore file.');}
    private function validRemote(string $url):bool{if(preg_match('/[\x00-\x20]/',$url))return false;if(preg_match('~^git@[A-Za-z0-9.-]+:[A-Za-z0-9._/-]+(?:\.git)?$~',$url))return true;$parts=parse_url($url);return is_array($parts)&&in_array(strtolower((string)($parts['scheme']??'')),['https','ssh'],true)&&empty($parts['user'])&&empty($parts['pass'])&&!empty($parts['host']);}
    private function run(array $command,string $cwd,bool $throw=true):array{$pipes=[];$process=proc_open($command,[0=>['pipe','r'],1=>['pipe','w'],2=>['pipe','w']],$pipes,$cwd,null);if(!is_resource($process))throw new \RuntimeException('Could not start Git.');fclose($pipes[0]);$out=stream_get_contents($pipes[1]);$err=stream_get_contents($pipes[2]);fclose($pipes[1]);fclose($pipes[2]);$code=proc_close($process);$result=['code'=>$code,'output'=>trim((string)$out),'error'=>trim((string)$err),'combined'=>trim((string)$out."\n".(string)$err)];if($throw&&$code!==0)throw new \RuntimeException('Git command failed: '.$result['combined']);return $result;}
}
