<?php

namespace iDServer\Message;

use DomainException;
use DOMDocument;
use DOMElement;
use DOMXPath;
use iDServer\AbstractKeyStore;
use iDServer\Constants;
use iDServer\Log;
use iDServer\Message\Strategy\AbstractMessageHandlingStrategy;
use iDServer\Saml\AuthnRequestData;
use iDServer\Saml\Status;

/**
 * Login request message
 */
class Login extends AbstractMessage
{
    /**
     *
     * @param AbstractKeyStore $keyStore
     * @param string $server
     * @param AuthnRequestData $data
     */
    public function __construct(AbstractKeyStore $keyStore, $server, AuthnRequestData $data)
    {
        parent::__construct($keyStore, $data);
        if (!$this->getData()->getDestination()) {
            $this->getData()->setDestination($server . '/saml/authnrequest');
        }
    }

    /**
     * @return string
     */
    public function getType(): string
    {
        return MessageType::LOGIN;
    }

    /**
     * Get DOMDocument instance of the unsigned AuthnRequest XML
     *
     * @param AbstractMessageHandlingStrategy $strategy
     * @return DOMDocument
     * @throws DomainException When no landing page for the response was provided
     */
    protected function getUnsignedXml(AbstractMessageHandlingStrategy $strategy): DOMDocument
    {
        /* @var $data AuthnRequestData */
        $data = $this->getData();
        if (empty($data->getAssertionConsumerServiceURL())) {
            throw new DomainException("Invalid login: no landing page for the response was provided");
        }

        $doc = $strategy->createAuthnRequest($data);

        Log::debug("Constructed unsigned xml:\n" . $doc->saveXML());
        return $doc;
    }

    /**
     * @param DOMDocument $doc
     * @return array
     */
    public static function receiveMessage(DOMDocument $doc): array
    {
        $xpath = new DOMXPath($doc);
        $xpath->registerNamespace('saml2p', Constants\NS_SAMLP);
        $xpath->registerNamespace('saml2', Constants\NS_SAML);

        $statuscode = $xpath
            ->query("saml2p:Status/saml2p:StatusCode[@Value]/@Value", $doc->documentElement)->item(0)
            ->nodeValue;

        $nodes = $doc->getElementsByTagName('Assertion');
        $assertions = array();
        for ($i = 0; $i < $nodes->length; ++$i) {
            $node = $nodes->item($i);
            /* @var $resp DOMElement */
            $resp = $xpath->query("./saml2:Subject/saml2:NameID", $node)->item(0);

            /* @var $cond DOMElement */
            $cond = $xpath->query("./saml2:Conditions", $node)->item(0);
            $audience = $xpath->query("./saml2:AudienceRestriction/saml2:Audience", $cond)->item(0);

            $ass = [
                'TypeID' => $resp->getAttribute('SPNameQualifier'),
                'NameID' => trim($resp->nodeValue),
                'NotBefore' => $cond->getAttribute('NotBefore'),
                'NotOnOrAfter' => $cond->getAttribute('NotOnOrAfter'),
                'Authority' => $audience->nodeValue
            ];
            $assertions[] = $ass;
        }

        $nodes = $xpath->query("./saml2p:Extensions/saml2:AttributeStatement/saml2:Attribute");
        $attribs = array();
        for ($i = 0; $i < $nodes->length; ++$i) {
            /* @var $node DOMElement */
            $node = $nodes->item($i);

            $name = $node->getAttribute('Name');

            /* @var $valNode DOMElement */
            $valNode = $node->getElementsByTagName('AttributeValue')->item(0);
            $value = !isset($valNode) || $valNode->getAttribute('nil') == 'true' ? null : $valNode->nodeValue;

            $attribs[] = [
                'Name' => $name,
                'Value' => $value
            ];
        }

        $result = [
            'Success' => ($statuscode === Status::SUCCESS),
            'MessageId' => $doc->documentElement->getAttribute('ID'),
            'SessionIndex' => $doc->documentElement->getAttribute('ID'),
            'MessageType' => MessageType::LOGIN_REPLY,
            'XML' => $doc
        ];

        if (!empty($assertions)) {
            $result['Assertion'] = $assertions[0];
        }
        if (!empty($attribs)) {
            $result['Attributes'] = $attribs;
        }

        Log::debug("Login result: " . print_r($result, true));

        return $result;
    }
}
