<?php
/**
 * Copyright 2020 Adobe
 * All Rights Reserved.
 *
 * NOTICE: Adobe permits you to use, modify, and distribute this file in
 * accordance with the terms of the Adobe license agreement accompanying
 * it.
 */
declare(strict_types=1);

namespace Magento\Mray\Api;

use InvalidArgumentException;
use RuntimeException;
use Magento\Mray\Index\Scanner\Probe\SubjectReader\Json;

class GetProjectVersion
{
    private const COMPOSER_LOCK = 'composer.lock';
    private const COMPOSER_JSON = 'composer.json';
    private const CORE_PACKAGE_NAMES = [
        'magento/product-enterprise-edition',
        'magento/magento2-ee-base',
        'magento/product-community-edition',
        'magento/magento2-base',
        'magento/magento-cloud-metapackage',
    ];

    /**
     * @var Json
     */
    private $jsonReader;

    /**
     * @param string $path
     * @return string
     */
    public function execute(string $path): string
    {
        try {
            return $this->getVersionFromComposerLock($path);
        } catch (\Exception $exception) {
            try {
                return $this->getFrameworkVersionFromComposerJson($path);
            } catch (\Exception $secondException) {
                throw new RuntimeException($exception->getMessage() . ' ' . $secondException->getMessage());
            }
        }
    }

    /**
     * @param string $path
     * @return string
     */
    private function getFrameworkVersionFromComposerJson(string $path): string
    {
        $fullPath = rtrim($path, '/') . '/' . self::COMPOSER_JSON;
        if (!file_exists($fullPath)) {
            throw new InvalidArgumentException(sprintf('File does not exist: %s', $fullPath));
        }

        $data = $this->getJsonReader()->read($fullPath);

        if (!isset($data['replace']['magento/framework'])) {
            throw new RuntimeException(sprintf('Cannot find "magento/framework" package replace in %s.', $fullPath));
        }

        if (!isset($data['version'])) {
            throw new RuntimeException(sprintf('Cannot find version in %s.', $fullPath));
        }

        return $data['version'];
    }

    /**
     * Extracts Magento version from composer.lock instead of composer.json,
     * as the later can have a range instead a specific version
     *
     * @param string $path
     * @return string
     * @throws InvalidArgumentException
     * @throws RuntimeException
     */
    private function getVersionFromComposerLock(string $path): string
    {
        $fullPath = rtrim($path, '/') . '/' . self::COMPOSER_LOCK;
        if (!file_exists($fullPath)) {
            throw new InvalidArgumentException(sprintf('File does not exist: %s', $fullPath));
        }

        $data = $this->getJsonReader()->read($fullPath);

        if (!isset($data['packages'])) {
            throw new RuntimeException(sprintf('Cannot find "packages" section in %s.', $fullPath));
        }

        foreach ($data['packages'] as $package) {
            if (isset($package['name'], $package['version']) && in_array($package['name'], self::CORE_PACKAGE_NAMES)) {
                return $package['version'];
            }

        }
        throw new RuntimeException(
            sprintf(
                'Cannot identify project version based on %s.',
                $fullPath
            )
        );
    }

    /**
     * @return Json
     */
    private function getJsonReader(): Json
    {
        if (!$this->jsonReader) {
            $this->jsonReader = new Json();
        }
        return $this->jsonReader;
    }
}
