<?php

namespace AuthStack\SAMLServiceProvider\tests\units\Model;

use AuthStack\SAMLServiceProvider\Model\AuthNResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use LightSaml\ClaimTypes;
use LightSaml\Helper;
use LightSaml\Model\Assertion\Attribute;
use LightSaml\Model\Assertion\AudienceRestriction;
use LightSaml\Model\Assertion\AuthnContext;
use LightSaml\Model\Assertion\Conditions;
use LightSaml\Model\Assertion\Issuer;
use LightSaml\Model\Assertion\NameID;
use LightSaml\Model\Assertion\SubjectConfirmation;
use LightSaml\Model\Assertion\SubjectConfirmationData;
use LightSaml\Model\Assertion\SubjectLocality;
use LightSaml\Model\Context\SerializationContext;
use LightSaml\Model\Metadata\EntityDescriptor;
use LightSaml\Model\Protocol\Status;
use LightSaml\Model\Protocol\StatusCode;
use LightSaml\SamlConstants;
use mageekguy\atoum;
use LightSaml\Model\Assertion\Subject;
use LightSaml\Model\Protocol\Response;
use LightSaml\Model\XmlDSig\Signature;
use LightSaml\Model\Assertion\Assertion;
use LightSaml\Model\Assertion\AttributeStatement;
use LightSaml\Model\Assertion\AuthnStatement;
use LightSaml\Model\Protocol\AuthnRequest;
use AuthStack\SAMLServiceProvider\Model\AuthN as Authentication;
use AuthStack\SAMLServiceProvider\Model\IdentityProvider;
use AuthStack\SAMLServiceProvider\Model\ServiceProvider;

class AuthN extends atoum
{
    public function testCreate()
    {
        $this->given($authn = new Authentication(true))
            ->then
            ->object($request = $authn->getAuthnRequest())->isInstanceOf(AuthnRequest::class)
            ->string($request->getIssuer()->getValue())->isEqualTo('https://sp.tld/saml/entity.xml')
            ->object($request->getSignature())->isInstanceOf(Signature::class)
            ->string($request->getDestination())->isEqualTo('https://www.authstack.co.uk/saml2.0/sso')
            ;
    }

    public function testReceiveSamlResponse()
    {
        $this->given($response = $this->createSAMLResponse($id = Helper::generateID()))
            ->and($ctx = new SerializationContext())
            ->and($response->serialize($ctx->getDocument(), $ctx))
            ->and($xml = $ctx->getDocument()->saveXML())
            ->and($request = new Request([
                'SAMLResponse' => base64_encode(gzdeflate($xml))], [], [], [], [], ['QUERY_STRING' => 'SAMLResponse=' . urlencode(base64_encode(gzdeflate($xml)))]))
            ->and($authn = new Authentication)
            ->then
                ->object($authn->receive($request))->isInstanceOf(Authentication::class)
                ->boolean($authn->validateAuthnResponse())->isTrue()
                ->object($resp = $authn->getAuthnResponse())->isInstanceOf(AuthNResponse::class)
                ->object($assertions = $resp->getAssertionCollection())->isInstanceOf(Collection::class)
                ->integer($assertions->count())->isEqualTo(1)
                ->object($attributes = $resp->getAttributeCollection())->isInstanceOf(Collection::class)
                ->integer($attributes->count())->isEqualTo(4)
                ->string($resp->getNameIdValue())->isEqualTo('dyson.snow@yahoo.com')
                ->string($resp->getNameIdFormat())->isEqualTo(SamlConstants::NAME_ID_FORMAT_EMAIL)
                ->string($resp->getAttributeCollection()->first()['name'])->isEqualTo('name')
                ->string($resp->getAttributeCollection()->first()['value'])->isEqualTo('Dyson')
                ->array($resp->getAttributeCollection()->last()['value'])->hasSize(3)
            ;
    }

    public function testSamlResponse()
    {
        $this->given($response = $this->createSAMLResponse($id = Helper::generateID()))
            ->and($ctx = new SerializationContext())
            ->and($response->serialize($ctx->getDocument(), $ctx))
            ->and($xml = $ctx->getDocument()->saveXML())
            ->then
            ->string($xml)->isNotEmpty()
            ;
    }

    protected function createSAMLResponse(string $in_response_to)
    {
        $sp = new ServiceProvider();
        $idp = new IdentityProvider('https://authstack');

        $response = new Response();
        $subject = new Subject();
        $confirmation = new SubjectConfirmation();
        $subjconfdata = new SubjectConfirmationData();
        $statement = new AuthnStatement();
        $attributes = new AttributeStatement();
        $assertion = new Assertion();

        $subject->setNameID(new NameID('dyson.snow@yahoo.com', SamlConstants::NAME_ID_FORMAT_EMAIL));
        $subject->addSubjectConfirmation($confirmation);

        $confirmation
            ->setMethod(SamlConstants::CONFIRMATION_METHOD_BEARER)
            ->setSubjectConfirmationData($subjconfdata);

        $subjconfdata->setNotOnOrAfter($sp->getUpperDT()->getTimestamp());
        $subjconfdata->setInResponseTo($in_response_to);
        $subjconfdata->setRecipient($sp->getACSUrl());

        $attributes->addAttribute(new Attribute('name', 'Dyson'));
        $attributes->addAttribute(new Attribute('last_name', 'Snow'));
        $attributes->addAttribute(new Attribute('email', 'dyson.snow@yahoo.com'));
        $attributes->addAttribute(new Attribute('permissions', ['item:read', 'item:write', 'item:delete']));

        $assertion
            ->setSubject($subject)
            ->addItem($attributes)
            ->addItem($statement)
            ->setId(Helper::generateID())
            ->setIssuer(new Issuer($idp->getEntityID()))
            ->setIssueInstant(time())
            ->setConditions(
                (new Conditions())
                ->setNotBefore(new \DateTime('now', new \DateTimeZone('UTC')))
                ->setNotOnOrAfter($sp->getUpperDT()->getTimestamp())
                ->addItem(
                    new AudienceRestriction([$sp->getEntityId(), $sp->getACSUrl()])
                )
            )
        ;

        if($sp->wantsSignedMessages())
        {
            $response->setSignature($idp->getSignatureWriter());
        }

        /*
        if($sp->wantsSignedAssertions())
        {
            $assertion->setSignature($idp->getSignatureWriter());
        }
        */

        $response->setSignature($idp->getSignatureWriter());
        $response->addAssertion($assertion);

        $statement
            ->setAuthnContext(
                (new AuthnContext())->setAuthnContextClassRef(SamlConstants::AUTHN_CONTEXT_UNSPECIFIED)
            )
            ->setAuthnInstant(time())
            ->setSubjectLocality(
            (new SubjectLocality())->setAddress('test')->setDNSName('dns name'))
        ;

        $response
            ->setID(Helper::generateID())
            ->setVersion('2.0')
            ->setIssueInstant(time())
            ->setIssuer(new Issuer($idp->getEntityID()))
            ->setConsent(SamlConstants::CONSENT_UNSPECIFIED)
            ->setStatus(new Status(new StatusCode(SamlConstants::STATUS_SUCCESS)))
            ->setInResponseTo($in_response_to)
            ->setDestination($sp->getACSUrl())
        ;

        return $response;
    }
}