<?php

namespace App\TestRunner;

use App\TestRunner\Contracts\TestSuiteInterface;

abstract class BaseTestSuite implements TestSuiteInterface
{
    abstract public function jiraId(): string;
    abstract public function sampleInput(): array;
    abstract public function custom(array $params): array;
    abstract protected function cases(array $params = []): array;
    abstract protected function execute(array $case);

    public function run(array $params = []): array
    {
        $results = [];
        $passed  = 0;
        $failed  = 0;

        foreach ($this->cases($params) as $case) {
            $result = $this->runCase($case);
            $result['passed'] ? $passed++ : $failed++;
            $results[] = $result;
        }

        return [
            'jira_id' => $this->jiraId(),
            'summary' => [
                'total'  => $passed + $failed,
                'passed' => $passed,
                'failed' => $failed,
                'status' => $failed === 0 ? 'PASS' : 'FAIL',
            ],
            'results' => $results,
        ];
    }

    protected function runCase(array $case): array
    {
        try {
            $actual = $this->execute($case);
            $passed = $this->assert($actual, $case['expected'], $case['assertType'] ?? 'equals');

            return [
                'suite'       => $case['suite'],
                'description' => $case['description'],
                'passed'      => $passed,
                'expected'    => $case['expected'],
                'actual'      => $actual,
            ];
        } catch (\Throwable $e) {
            return [
                'suite'       => $case['suite'],
                'description' => $case['description'],
                'passed'      => false,
                'expected'    => $case['expected'],
                'actual'      => 'EXCEPTION: ' . $e->getMessage(),
            ];
        }
    }

    protected function assert($actual, $expected, string $type): bool
    {
        if ($type === 'closeTo') {
            $a = is_array($actual)   ? (float)($actual['minimumAmount']   ?? 0) : (float)$actual;
            $e = is_array($expected) ? (float)($expected['minimumAmount'] ?? 0) : (float)$expected;
            return abs($a - $e) < 0.01;
        }

        // check only the 'valid' key of an array result against a bool
        if ($type === 'keyEquals') {
            $actualValid = is_array($actual) ? ($actual['valid'] ?? null) : $actual;
            return $actualValid === $expected;
        }

        // check only the 'alertMessage' key of an array result against a string
        if ($type === 'alertMessageEquals') {
            $msg = is_array($actual) ? ($actual['alertMessage'] ?? null) : null;
            return $msg === $expected;
        }

        // check only the 'minimumAmount' key of an array result
        if ($type === 'minimumAmountEquals') {
            $min = is_array($actual) ? $actual['minimumAmount'] : $actual;
            return $min === $expected;
        }

        if (is_array($actual) && is_array($expected)) {
            return $actual == $expected;
        }

        return $actual === $expected;
    }
}
