<?php
declare(strict_types=1);
namespace Aen\Console;
use RuntimeException;
final class Repository
{
    public function __construct(private string $root) {}
    public function csv(string $file): array
    {
        if (!is_file($file) || !is_readable($file) || filesize($file) > 1048576) throw new RuntimeException('CSV недоступен или больше 1 МБ.');
        $stream=fopen($file,'rb'); if (!$stream) throw new RuntimeException('Не удалось открыть CSV.');
        try {
            $head=fgetcsv($stream,0,',','"','');
            if ($head!==['name','email','city']) throw new RuntimeException('Заголовок CSV должен быть name,email,city.');
            $rows=[];$emails=[];$line=1;
            while (($fields=fgetcsv($stream,0,',','"',''))!==false) {
                $line++;if ($fields===[null]) continue;
                if (count($fields)!==3) throw new RuntimeException("Строка $line: нужно три поля.");
                if(!mb_check_encoding(implode('',$fields),'UTF-8'))throw new RuntimeException("Строка $line: требуется UTF-8.");
                [$name,$email,$city]=array_map('trim',$fields);$email=strtolower($email);
                if ($name===''||mb_strlen($name)>80||!filter_var($email,FILTER_VALIDATE_EMAIL)||strlen($email)>120||!in_array($city,['Москва','Казань','Тверь'],true)) throw new RuntimeException("Строка $line: проверьте имя, email и город.");
                if(isset($emails[$email]))throw new RuntimeException("Строка $line: повторный email в CSV.");
                $emails[$email]=true;$rows[]=compact('name','email','city');if(count($rows)>200)throw new RuntimeException('Не больше 200 контактов за импорт.');
            }
            if(!$rows)throw new RuntimeException('CSV не содержит контактов.');return $rows;
        } finally { fclose($stream); }
    }
    public function read(): array
    {
        $file=$this->root.'/contacts.json';if(!is_file($file))return [];
        $data=json_decode((string)file_get_contents($file),true,512,JSON_THROW_ON_ERROR);
        if(!is_array($data)||!array_is_list($data))throw new RuntimeException('Повреждён локальный JSON контактов.');return $data;
    }
    public function import(array $rows,bool $dryRun): array
    {
        if($dryRun)return $this->plan($this->read(),$rows)['result'];
        if(!is_dir($this->root)&&!mkdir($this->root,0700,true)&&!is_dir($this->root))throw new RuntimeException('Не удалось создать учебный runtime.');
        $lock=fopen($this->root.'/import.lock','c');if(!$lock)throw new RuntimeException('Не удалось открыть блокировку.');
        try {
            if(!flock($lock,LOCK_EX|LOCK_NB))throw new RuntimeException('Другой импорт уже выполняется. Повторите позже.');
            $plan=$this->plan($this->read(),$rows);$temp=tempnam($this->root,'import-');if(!$temp)throw new RuntimeException('Не удалось создать временный файл.');
            try { $json=json_encode($plan['rows'],JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR)."\n";if(file_put_contents($temp,$json,LOCK_EX)!==strlen($json))throw new RuntimeException('Не удалось полностью записать JSON.');if(!rename($temp,$this->root.'/contacts.json'))throw new RuntimeException('Не удалось заменить JSON.'); }
            finally { if(is_file($temp))unlink($temp); }
            return $plan['result'];
        } finally { flock($lock,LOCK_UN);fclose($lock); }
    }
    private function plan(array $current,array $rows): array
    {
        $known=array_fill_keys(array_column($current,'email'),true);$added=0;$skipped=0;
        foreach($rows as $row){if(isset($known[$row['email']])){$skipped++;continue;}$current[]=$row;$known[$row['email']]=true;$added++;}
        if(count($current)>500)throw new RuntimeException('Учебный runtime ограничен 500 контактами.');
        return ['rows'=>$current,'result'=>['added'=>$added,'skipped'=>$skipped,'total'=>count($current)]];
    }
}
