<?php

namespace Icinga\Module\Toplevelview\Model;

use Icinga\Module\Icingadb\Common\Auth;
use Icinga\Module\Icingadb\Model\Host;
use Icinga\Module\Icingadb\Model\Service;
use Icinga\Module\Icingadb\Model\Hostgroup;

use ipl\Orm\Behavior\Binary;
use ipl\Orm\Behaviors;
use ipl\Orm\Query;
use ipl\Orm\Relations;
use ipl\Orm\UnionModel;
use ipl\Orm\UnionQuery;
use ipl\Sql\Adapter\Pgsql;
use ipl\Sql\Connection;
use ipl\Sql\Expression;
use ipl\Sql\Select;

/**
 * Hostgroupsummary is a custom model we use to add more columns than the IcingaDB Hostgroupsummary
 */
class Hostgroupsummary extends UnionModel
{
    public static function on(Connection $db): UnionQuery
    {
        $q = parent::on($db);

        $q->on(
            Query::ON_SELECT_ASSEMBLED,
            function () use ($q) {
                $auth = new class () {
                    use Auth;
                };

                $auth->assertColumnRestrictions($q->getFilter());
            }
        );

        $q->on($q::ON_SELECT_ASSEMBLED, function (Select $select) use ($q) {
            $model = $q->getModel();

            $groupBy = $q->getResolver()->qualifyColumnsAndAliases((array) $model->getKeyName(), $model, false);

            // For PostgreSQL, ALL non-aggregate SELECT columns must appear in the GROUP BY clause:
            if ($q->getDb()->getAdapter() instanceof Pgsql) {
                /**
                 * Ignore Expressions, i.e. aggregate functions {@see getColumns()},
                 * which do not need to be added to the GROUP BY.
                 */
                $candidates = array_filter($select->getColumns(), 'is_string');
                // Remove already considered columns for the GROUP BY, i.e. the primary key.
                $candidates = array_diff_assoc($candidates, $groupBy);
                $groupBy = array_merge($groupBy, $candidates);
            }

            $select->groupBy($groupBy);
        });

        return $q;
    }

    public function getTableName()
    {
        return 'hostgroup';
    }

    public function getKeyName()
    {
        return ['id' => 'hostgroup_id'];
    }

    public function getColumns()
    {
        return [
            'name'                        => 'hostgroup_name',
            'name_ci'                     => 'hostgroup_name_ci',
            'display_name'                => 'hostgroup_display_name',
            'hosts_down_handled'          => new Expression(
                'SUM(CASE WHEN host_state = 1'
                . ' AND (host_handled = \'y\' OR host_reachable = \'n\') THEN 1 ELSE 0 END)'
            ),
            'hosts_down_unhandled'        => new Expression(
                'SUM(CASE WHEN host_state = 1'
                . ' AND host_handled = \'n\' AND host_reachable = \'y\' THEN 1 ELSE 0 END)'
            ),
            'hosts_pending'               => new Expression(
                'SUM(CASE WHEN host_state = 99 THEN 1 ELSE 0 END)'
            ),
            'hosts_total'                 => new Expression(
                'SUM(CASE WHEN host_id IS NOT NULL THEN 1 ELSE 0 END)'
            ),
            'hosts_up'                    => new Expression(
                'SUM(CASE WHEN host_state = 0 THEN 1 ELSE 0 END)'
            ),
            'hosts_severity'              => new Expression('MAX(host_severity)'),
            'services_critical_handled'   => new Expression(
                'SUM(CASE WHEN service_state = 2'
                . ' AND (service_handled = \'y\' OR service_reachable = \'n\') THEN 1 ELSE 0 END)'
            ),
            'services_critical_unhandled' => new Expression(
                'SUM(CASE WHEN service_state = 2'
                . ' AND service_handled = \'n\' AND service_reachable = \'y\' THEN 1 ELSE 0 END)'
            ),
            'services_ok'                 => new Expression(
                'SUM(CASE WHEN service_state = 0 THEN 1 ELSE 0 END)'
            ),
            'services_pending'            => new Expression(
                'SUM(CASE WHEN service_state = 99 THEN 1 ELSE 0 END)'
            ),
            'services_total'              => new Expression(
                'SUM(CASE WHEN service_id IS NOT NULL THEN 1 ELSE 0 END)'
            ),
            'services_unknown_handled'    => new Expression(
                'SUM(CASE WHEN service_state = 3'
                . ' AND (service_handled = \'y\' OR service_reachable = \'n\') THEN 1 ELSE 0 END)'
            ),
            'services_unknown_unhandled'  => new Expression(
                'SUM(CASE WHEN service_state = 3'
                . ' AND service_handled = \'n\' AND service_reachable = \'y\' THEN 1 ELSE 0 END)'
            ),
            'services_warning_handled'    => new Expression(
                'SUM(CASE WHEN service_state = 1'
                . ' AND (service_handled = \'y\' OR service_reachable = \'n\') THEN 1 ELSE 0 END)'
            ),
            'services_warning_unhandled'  => new Expression(
                'SUM(CASE WHEN service_state = 1'
                . ' AND service_handled = \'n\' AND service_reachable = \'y\' THEN 1 ELSE 0 END)'
            ),
            'hosts_downtime_handled' => new Expression(
                'SUM(CASE WHEN host_state != 1 AND host_in_downtime = \'y\' THEN 1 ELSE 0 END)'
            ),
            'hosts_downtime_active' => new Expression(
                'SUM(CASE WHEN host_in_downtime = \'y\' THEN 1 ELSE 0 END)'
            ),
            'services_downtime_handled' => new Expression(
                'SUM(CASE WHEN service_state != 1 AND service_in_downtime = \'y\' THEN 1 ELSE 0 END)'
            ),
            'services_downtime_active' => new Expression(
                'SUM(CASE WHEN service_in_downtime = \'y\' THEN 1 ELSE 0 END)'
            ),
        ];
    }

    public function getSearchColumns()
    {
        return ['name_ci', 'display_name'];
    }

    public function getDefaultSort()
    {
        return 'display_name';
    }

    public function getUnions(): array
    {
        $unions = [
            [
                Host::class,
                [
                    'hostgroup',
                    'state'
                ],
                [
                    'hostgroup_id'           => 'hostgroup.id',
                    'hostgroup_name'         => 'hostgroup.name',
                    'hostgroup_name_ci'      => 'hostgroup.name_ci',
                    'hostgroup_display_name' => 'hostgroup.display_name',
                    'host_id'                => 'host.id',
                    'host_state'             => 'state.hard_state',
                    'host_handled'           => 'state.is_handled',
                    'host_reachable'         => 'state.is_reachable',
                    'host_severity'          => 'state.severity',
                    'host_in_downtime'       => 'state.in_downtime',
                    'service_id'             => new Expression('NULL'),
                    'service_state'          => new Expression('NULL'),
                    'service_handled'        => new Expression('NULL'),
                    'service_reachable'      => new Expression('NULL'),
                    'service_in_downtime'    => new Expression('NULL'),
                ]
            ],
            [
                Service::class,
                [
                    'hostgroup',
                    'state'
                ],
                [
                    'hostgroup_id'           => 'hostgroup.id',
                    'hostgroup_name'         => 'hostgroup.name',
                    'hostgroup_name_ci'      => 'hostgroup.name_ci',
                    'hostgroup_display_name' => 'hostgroup.display_name',
                    'host_id'                => new Expression('NULL'),
                    'host_state'             => new Expression('NULL'),
                    'host_handled'           => new Expression('NULL'),
                    'host_reachable'         => new Expression('NULL'),
                    'host_in_downtime'       => new Expression('NULL'),
                    'host_severity'          => new Expression('0'),
                    'service_id'             => 'service.id',
                    'service_state'          => 'state.hard_state',
                    'service_handled'        => 'state.is_handled',
                    'service_reachable'      => 'state.is_reachable',
                    'service_in_downtime'    => 'state.in_downtime',
                ]
            ],
            [
                Hostgroup::class,
                [],
                [
                    'hostgroup_id'           => 'hostgroup.id',
                    'hostgroup_name'         => 'hostgroup.name',
                    'hostgroup_name_ci'      => 'hostgroup.name_ci',
                    'hostgroup_display_name' => 'hostgroup.display_name',
                    'host_id'                => new Expression('NULL'),
                    'host_state'             => new Expression('NULL'),
                    'host_handled'           => new Expression('NULL'),
                    'host_reachable'         => new Expression('NULL'),
                    'host_severity'          => new Expression('0'),
                    'host_in_downtime'       => new Expression('NULL'),
                    'service_id'             => new Expression('NULL'),
                    'service_state'          => new Expression('NULL'),
                    'service_handled'        => new Expression('NULL'),
                    'service_reachable'      => new Expression('NULL'),
                    'service_in_downtime'    => new Expression('NULL'),
                ]
            ]
        ];

        return $unions;
    }

    public function createBehaviors(Behaviors $behaviors)
    {
        $behaviors->add(new Binary([
            'id'
        ]));

        // This is because there is no better way
        (new Hostgroup())->createBehaviors($behaviors);
    }

    public function createRelations(Relations $relations)
    {
        // This is because there is no better way
        (new Hostgroup())->createRelations($relations);
    }

    public function getColumnDefinitions()
    {
        // This is because there is no better way
        return (new Hostgroup())->getColumnDefinitions();
    }
}
