<?php

namespace Build\Entities;

class ProjectEntity
{
    private $repositoryUrl;
    private $branch;
    private $username;
    private $password;
    private $sshPrivateKey;
    private $name;
    private $authenticationType;
    private $isLocal;
    private $isDeployedLocally;
    private $sshKeyId;
    private $changedItems = [];
    private $action;
    private $classicPolicySet;

    public function getSshKeyId()
    {
        return $this->sshKeyId;
    }

    public function setSshKeyId($sshKeyId)
    {
        $this->sshKeyId = $sshKeyId;
    }

    public static function fromRequest($obj): ProjectEntity
    {
       return  self::fromArray((array) $obj);
    }

    public static function fromArray(array $data): ProjectEntity
    {
        $item = new ProjectEntity();

        foreach ($data as $key => $value) {
            $key = underScoreToCamelCase($key);
            if (property_exists($item, $key)) {
                $item->{"set" . ucfirst($key)}($value);
                $item->setChangedItem($key);
            }
        }

        return $item;
    }

    public function combine(ProjectEntity $combineWith)
    {
        foreach ($combineWith->changedItemsToArray() as $key => $value) {
            $this->{"set" . ucfirst(underScoreToCamelCase($key))}($value);
        }
    }

    public function toArray(): array
    {
        return [
            'repository_url' => $this->getRepositoryUrl(),
            'branch' => $this->getBranch(),
            'name' => $this->getName(),
            'authentication_type' => $this->getAuthenticationType(),
            'username' => $this->getUsername(),
            'password' => $this->getPassword(),
            'ssh_private_key' => $this->getSshPrivateKey(),
            'is_local' => $this->isLocal() ? 'true' : 'false',
            'is_deployed_locally' => $this->isDeployedLocally() ? 'true' : 'false',
            'classic_policy_set' => $this->getClassicPolicySet(),
            'ssh_key_id' => $this->getSshKeyId(),
        ];
    }

    public function changedItemsToArray(): array
    {
        $data = [];
        foreach ($this->getChangedItems() as $item) {
            $data[camelCaseToUnderScore($item)] = $this->{"get" . ucfirst($item)}();
        }
        return $data;
    }

    private function setChangedItem($key)
    {
        $this->changedItems[] = $key;
    }

    /**
     * @return mixed
     */
    public function getRepositoryUrl()
    {
        return $this->repositoryUrl;
    }

    /**
     * @param mixed $repositoryUrl
     */
    public function setRepositoryUrl($repositoryUrl)
    {
        $this->repositoryUrl = $repositoryUrl;
    }

    /**
     * @return mixed
     */
    public function getBranch()
    {
        return $this->branch;
    }

    /**
     * @param mixed $branch
     */
    public function setBranch($branch)
    {
        $this->branch = $branch;
    }

    /**
     * @return mixed
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param mixed $name
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * @return mixed
     */
    public function getAction()
    {
        return $this->action;
    }

    /**
     * @param mixed $name
     */
    public function setAction($action)
    {
        $this->action = $action;
    }

    /**
     * @return mixed
     */
    public function getAuthenticationType()
    {
        return $this->authenticationType;
    }

    /**
     * @param mixed $authenticationType
     */
    public function setAuthenticationType($authenticationType)
    {
        $this->authenticationType = $authenticationType;
    }

    /**
     * @return mixed
     */
    public function getUsername()
    {
        return $this->username;
    }

    /**
     * @param mixed $username
     */
    public function setUsername($username)
    {
        $this->username = $username;
    }

    /**
     * @return mixed
     */
    public function getPassword()
    {
        return $this->password;
    }

    /**
     * @param mixed $password
     */
    public function setPassword($password)
    {
        $this->password = $password;
    }

    /**
     * @return mixed
     */
    public function getSshPrivateKey()
    {
        return $this->sshPrivateKey;
    }

    /**
     * @param mixed $sshPrivateKey
     */
    public function setSshPrivateKey($sshPrivateKey)
    {
        $this->sshPrivateKey = $sshPrivateKey;
    }

    /**
     * @return bool
     */
    public function isLocal(): bool|null
    {
        return $this->isLocal;
    }

    /**
     * @return bool
     */
    public function getIsLocal(): string
    {
        return $this->isLocal() ? 'true' : 'false';
    }

    /**
     * @return bool
     */
    public function isDeployedLocally(): bool|null
    {
        return $this->isDeployedLocally;
    }

    public function getClassicPolicySet(): string
    {
        return $this->classicPolicySet ? 'true' : 'false';
    }

    public function isClassicPolicySet(): bool
    {
        return !!$this->classicPolicySet;
    }

    /**
     * @param bool $value
     */
    public function setClassicPolicySet(bool $value)
    {
        $this->classicPolicySet = is_bool($value) ? $value : $value === 'true';
    }
    

    /**
     * @return string
     */
    public function getIsDeployedLocally(): string
    {
        return $this->isDeployedLocally() ? 'true' : 'false';
    }

    public function getChangedItems(): array
    {
        return $this->changedItems;
    }

    /**
     * @param bool $isLocal
     */
    public function setIsLocal(bool|string $isLocal)
    {
        if (is_bool($isLocal)) {
            $this->isLocal = $isLocal;
        } else {
            $this->isLocal = $isLocal === 'true';
        }
    }

    /**
     * @param bool $isDeployedLocally
     */
    public function setIsDeployedLocally(bool|string $isDeployedLocally)
    {
        if (is_bool($isDeployedLocally)) {
            $this->isDeployedLocally = $isDeployedLocally;
        } else {
            $this->isDeployedLocally = $isDeployedLocally === 'true';
        }
    }
}


class ProjectEntityValidator
{
    const PASS_AUTHENTICATION = 'password';
    const KEY_AUTHENTICATION = 'private_key';
    const AUTHENTICATION_TYPES = [self::PASS_AUTHENTICATION, self::KEY_AUTHENTICATION];
    const REQUIRED_FIELDS = ['repositoryUrl', 'branch', 'authenticationType'];

    public static function validateBeforeInsertion(ProjectEntity $entity)
    {
        self::validateRequiredFields($entity);
        self::validateAuthenticationType($entity);
        self::validateRepositoryUrl($entity);
    }

    public static function validateRequiredFields(ProjectEntity $entity)
    {
        foreach (self::REQUIRED_FIELDS as $field) {
            if (empty($entity->{"get" . ucfirst($field)}())) {
                throw new \InvalidArgumentException("$field field is required");
            }
        }
    }

    public static function validateAuthenticationType(ProjectEntity $entity)
    {
        if (!in_array($entity->getAuthenticationType(), self::AUTHENTICATION_TYPES)) {
            throw new \InvalidArgumentException('Wrong authentication type. Allowed values: ' . implode(', ', self::AUTHENTICATION_TYPES));
        }

        if ($entity->getAuthenticationType() == self::PASS_AUTHENTICATION && (empty($entity->getPassword()) || empty($entity->getUsername()))) {
            throw new \InvalidArgumentException('Password and Username are required fields.');
        }

        if (
            $entity->getAuthenticationType() == self::KEY_AUTHENTICATION &&
            (empty($entity->getSshPrivateKey()) && empty($entity->getSshKeyId()))
        ) {
            throw new \InvalidArgumentException('Private ssh key is required field.');
        }
    }

    public static function validateRepositoryUrl(ProjectEntity $entity)
    {
        if (!preg_match('/^(?:git|ssh|https?|git@(.*)):(\/\/)?(.*)/', $entity->getRepositoryUrl())) {
            throw new \InvalidArgumentException('Malformed repository URL. Expected a valid git URL. Supported protocols: git, ssh, http(s)');
        }
    }

    public static function validateBeforeUpdating(ProjectEntity $entity)
    {
        if (in_array('authenticationType', $entity->getChangedItems())) {
            self::validateAuthenticationType($entity);
        }

        if (in_array('repositoryUrl', $entity->getChangedItems())) {
            self::validateRepositoryUrl($entity);
        }
    }
}
