#!/usr/bin/env php
<?php

declare(strict_types=1);

use Aws\CognitoIdentity\CognitoIdentityClient;
use Aws\CognitoIdentity\CognitoIdentityProvider;
use Aws\CognitoIdentityProvider\CognitoIdentityProviderClient;
use CliArgs\CliArgs;
use gfaugere\Monolog\Formatter\ColoredLineFormatter;
use League\Container\Container;
use Monolog\Handler\ErrorLogHandler;
use Monolog\Logger;
use Monolog\Processor\PsrLogMessageProcessor;
use Psr\Log\LoggerAwareInterface;
use Swissport\Serverless\EventRunner;
use Swissport\Serverless\Helper\Cognito;
use Swissport\Serverless\Runtime\Serverless\Bref;
use Swissport\Serverless\Runtime\Serverless\InvokeLocal;
use Swissport\Serverless\Helper\StateTracker;
use Swissport\Serverless\Runtime\RuntimeInterface;

if (file_exists(__DIR__ . '/vendor/autoload.php')) {
    require_once __DIR__ . '/vendor/autoload.php';
} elseif (file_exists(__DIR__ . '/../autoload.php')) {
    /** @noinspection PhpIncludeInspection */
    require_once __DIR__ . '/../autoload.php';
} else {
    /** @noinspection PhpIncludeInspection */
    require_once __DIR__ . '/../../autoload.php';
}

/**
 * Only fall back to putting the AWS_REGION env var if it is not already present
 *
 * @see https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html
 *
 * "If defined, this environment variable overrides the values in the environment variable AWS_DEFAULT_REGION and the
 *  profile setting `region`."
 */
if (getenv('AWS_REGION') === false) {
    /**
     * Try to set from AWS_DEFAULT_REGION
     *
     * @see https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html
     *
     * "If defined, this environment variable overrides the value for the profile setting `region`."
     */
    if (getenv('AWS_DEFAULT_REGION') !== false) {
        $defaultRegion = getenv('AWS_DEFAULT_REGION');
        putenv("AWS_REGION={$defaultRegion}");
    } else {
        // Set up the AWS_REGION environment variable from the aws configuration file.
        $awsConfigFilePath = getenv('HOME') . '/.aws/config';
        if (file_exists($awsConfigFilePath)) {
            /**
             * Parse the config file with section support to extract config by profile if profiles exist
             *
             * @see https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
             * @see https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html
             */
            $config = parse_ini_file($awsConfigPath, true);
            // Fall back to 'default' profile if AWS_PROFILE is not set
            $profile = getenv('AWS_PROFILE') ?? 'default';
            if (!empty(($config[getenv('AWS_PROFILE')]['region'] ?? null))) {
                // If the config contains a region for the given profile, use it
                $region = $config[getenv('AWS_PROFILE')]['region'];
            } else {
                // The config is not sectioned by profile, so use the first region parse_ini_file found
                $region = $config['region'];
            }
            putenv("AWS_REGION=$region");
        } else {
            putenv('AWS_REGION=ap-southeast-2');
        }
    }
}

$config = [
    'runtimes' => [
        'bref' => Bref::class,
        'invoke' => InvokeLocal::class,
    ],
    'cliArgs' => [
        'user-data' => [
            'filter' => 'json',
            'help' => 'A json string representing a user',
        ],
        'user-file' => [
            'help' => 'A path to a file representing a user',
        ],
        'mode' => [
            'alias' => 'm',
            'filter' => [Bref::ID, InvokeLocal::ID],
            'default' => Bref::ID,
            'help' => 'The execution mode. bref or invoke',
        ],
        'stop-on-error' => [
            'alias' => 's',
            'filter' => 'flag',
            'help' => 'Stop on first error',
        ],
        'force-clean' => [
            'filter' => 'flag',
            'help' => 'Force database clean between each event test.'
        ],
        'function' => [
            'alias' => 'f',
            'help' => 'Specify the function to test against.  If not supplied, function will be decided based on the ' .
                'first configured function, or by event configuration.'
        ],
        'v' => [
            'filter' => 'flag',
            'help' => 'Verbose.  Emits log level info and higher',
        ],
        'vv' => [
            'filter' => 'flag',
            'help' => 'More Verbose.  Emits log level debug and higher',
        ],
        'event-path' => [
            'alias' => 'e',
            'help' => 'Path to event file or files to test.',
        ],
        'app-path' => [
            'alias' => 'a',
            'help' => 'Path to the app to test against.',
        ],
        'test' => [
            'alias' => 't',
            'filter' => 'flag',
            'help' => 'Test events in addition to running them',
        ],
        'help' => [
            'alias' => 'h',
            'filter' => 'flag',
            'help' => 'Show help',
        ],
        'keep-config' => [
            'filter' => 'flag',
            'help' => 'Keep resolved serverless config file after running'
        ],
        'defined-functions-only' => [
            'filter' => 'flag',
            'help' => 'Prevents events with no function configured from being run'
        ],
        'cognito-login' => [
            'filter' => 'flag',
            'help' => 'Attempt to authenticate via Cognito. If this flag is sent and cc or ccf are not provided, the event runner will prompt for a username / password'
        ],
        'cc' => [
            'help' => 'Cognito credentials in the form username:password:clientId'
        ],
        'ccf' => [
            'help' => 'Path to a Cognito credentials file in json format'
        ],
        'ccid' => [
            'help' => 'Provide a Cognito client ID'
        ]
    ],
];

foreach ($config['runtimes'] as $runtimeClass) {
    if (defined("{$runtimeClass}::ARGS")) {
        $config['cliArgs'] = array_merge(
            $config['cliArgs'],
            $runtimeClass::ARGS
        );
    }
}

$container = new Container();
$container->defaultToShared(true);
$container->add(StateTracker::class);

$container->add(CliArgs::class)
    ->addArgument($config['cliArgs']);

/** @var CliArgs */
$args = $container->get(CliArgs::class);

switch (true) {
    case ($args->isFlagExist('vv')):
        $loggerVerbosity = Logger::DEBUG;
        break;
    case ($args->isFlagExist('v')):
        $loggerVerbosity = Logger::INFO;
        break;
    default:
        $loggerVerbosity = Logger::NOTICE;
}

$lineFormat = "%color_start%[%datetime%] %message%%color_end% %context%";
$dateFormat = "Y-m-d H:i:s";

$container->add(ColoredLineFormatter::class)
    ->addArguments([$lineFormat, $dateFormat]);
$container->add(ErrorLogHandler::class)
    ->addArguments([ErrorLogHandler::OPERATING_SYSTEM, $loggerVerbosity])
    ->addMethodCall('setFormatter', [ColoredLineFormatter::class]);
$container->add(PsrLogMessageProcessor::class);
$container->add(Logger::class)
    ->addArgument('event-runner')
    ->addMethodCall('pushHandler', [ErrorLogHandler::class])
    ->addMethodCall('pushProcessor', [PsrLogMessageProcessor::class])
    ->setShared(true);
$container->add(CognitoIdentityProviderClient::class)
    ->addArgument([
        'version' => '2016-04-18',
        'region' => getenv('AWS_REGION'),
    ]);
$container->add(Cognito::class)
    ->addArgument(CognitoIdentityProviderClient::class);

$container->inflector(LoggerAwareInterface::class)
    ->invokeMethod('setLogger', [Logger::class]);

if ($args->isFlagExist('h')) {
    print $args->getHelp();
    exit;
}

$runtime = $config['runtimes'][$args->getArg('m')];

$container->add(RuntimeInterface::class, $runtime)
    ->addArguments([CliArgs::class, StateTracker::class]);

$container->add(EventRunner::class)
    ->addArguments([
        CliArgs::class,
        StateTracker::class,
        RuntimeInterface::class,
        Cognito::class
    ]);

/** @var EventRunner */
$eventRunner = $container->get(EventRunner::class);

$eventRunner->runTests();