<?php

namespace Soft_Tehnica\Freya_Sync_Service\Services;

use Soft_Tehnica\Freya_Sync_Service\Data\Location;
use Soft_Tehnica\Freya_Sync_Service\Data\Notification;
use Soft_Tehnica\Freya_Sync_Service\Data\Tax;
use Soft_Tehnica\Freya_Sync_Service\Data\UserAddress;
use Soft_Tehnica\Freya_Sync_Service\Data\UserAddressInputs;

class SessionService
{

    private ?string $userAddress                  = null;
    private ?string $locationUid                  = null;
    private bool $addressModalDismissed           = false;
    private ?UserAddressInputs $userAddressInputs = null;
    private bool $isPickup                        = false;
    private ?UserAddress $address                 = null;
    private ?string $locationName                 = null;
    private ?Tax $tax                             = null;
    /** @var array<int, Location> */
    private array $fetchedLocations  = [];
    private bool $_flashNeedLocation = false;
    /** @var list<Notification> */
    private array $notifications = [];

    public function getUserAddress(): ?string
    {
        return $this->userAddress;
    }

    public function setUserAddress( ?string $userAddress ): void
    {
        $this->userAddress = $userAddress;
        $this->updateSession();
    }

    public function getLocationUid(): ?string
    {
        return $this->locationUid;
    }

    public function setLocationUid( ?string $locationUid ): void
    {
        $this->locationUid = $locationUid;
        $this->updateSession();
    }

    public function isAddressModalDismissed(): bool
    {
        return $this->addressModalDismissed;
    }

    public function setAddressModalDismissed(
        bool $addressModalDismissed
    ): void {
        $this->addressModalDismissed = $addressModalDismissed;
        $this->updateSession();
    }

    public function getUserAddressInputs(): ?UserAddressInputs
    {
        return $this->userAddressInputs;
    }

    public function setUserAddressInputs(
        ?UserAddressInputs $userAddressInputs
    ): void {
        $this->userAddressInputs = $userAddressInputs;
        $this->updateSession();
    }

    public function isPickup(): bool
    {
        return $this->isPickup;
    }

    public function setIsPickup( bool $isPickup ): void
    {
        $this->isPickup = $isPickup;
        $this->updateSession();
    }

    public function getAddress(): ?UserAddress
    {
        return $this->address ?? null;
    }

    public function setAddress( ?UserAddress $userAddress ): void
    {
        $this->address = $userAddress;
        $this->updateSession();
    }

    public function getLocationName(): ?string
    {
        return $this->locationName ?? null;
    }

    public function setLocationName( ?string $locationName ): void
    {
        $this->locationName = $locationName;
        $this->updateSession();
    }

    public function getTax(): ?Tax
    {
        return $this->tax ?? null;
    }

    public function setTax( ?Tax $tax ): void
    {
        $this->tax = $tax;
        $this->updateSession();
    }

    public function flashNeedLocation(): bool
    {
        return $this->_flashNeedLocation;
    }

    public function setFlashNeedLocation( bool $value ): void
    {
        if ( $value === $this->_flashNeedLocation ) {
            return;
        }

        $this->_flashNeedLocation = $value;
        $this->updateSession();
    }

    /**
     * @param Notification[] $notifications
     */
    public function setNotifications( array $notifications ): void
    {
        $this->notifications = $notifications;
        $this->updateSession();
    }

    /**
     * @return Notification[]
     */
    public function getNotifications(): array
    {
        return $this->notifications;
    }

    private function updateSession(): void
    {
        if (!headers_sent() && session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        try {
            if ( ! isset( $_SESSION['freya-sync-service'] ) ) {
                $_SESSION['freya-sync-service'] = [];
            }
            $session                             = [];
            $session['userAddress']              = $this->userAddress;
            $session['locationUid']              = $this->locationUid;
            $session['locationName']             = $this->locationName;
            $session['isPickup']                 = $this->isPickup;
            $session['addressModalWasDismissed'] = $this->addressModalDismissed;
            $session['fetchedLocations-2']       = serialize( $this->fetchedLocations );

            if ( isset( $this->userAddressInputs ) ) {
                $session['userAddressInputs'] = serialize( $this->userAddressInputs );
            }

            if ( isset( $this->address ) ) {
                $session['geo'] = serialize( $this->address );
            }

            if ( isset( $this->tax ) ) {
                $session['tax'] = serialize( $this->tax );
            }

            if ( count( $this->notifications ) > 0 ) {
                $session['notifications'] = serialize( $this->notifications );
            }

            $_SESSION['freya-sync-service'] = $session;
        } finally {
            session_write_close();
            $this->clearDuplicateCookies();
        }
    }

    private static SessionService $instance;

    protected function __construct()
    {
    }

    public function getFetchedLocations(): array
    {
        return $this->fetchedLocations;
    }

    /**
     * @param array<int, Location> $fetchedLocations
     */
    public function setFetchedLocations( array $fetchedLocations ): void
    {
        $this->fetchedLocations = $fetchedLocations;
        $this->updateSession();
    }

    protected function __clone()
    {
    }

    public function updateSessionWithActiveShipping(
        bool $delivery,
        bool $pickup
    ): void {
        if ( $delivery && $pickup ) {
            return;
        }

        if ( $delivery ) {
            $this->isPickup = false;
        }

        if ( $pickup ) {
            $this->address           = null;
            $this->userAddressInputs = null;
            $this->userAddress       = null;
            $this->tax               = null;
        }

        $this->updateSession();
    }

    public function refreshSession(): void
    {
        if ( ! isset( $_SESSION['freya-sync-service'] ) ) {
            $_SESSION['freya-sync-service'] = [];
        }

        if ( ! isset( $_SESSION['freya-sync-service']['CREATED'] ) ) {
            $_SESSION['freya-sync-service']['CREATED'] = time();

            return;
        }

        if ( time() - $_SESSION['freya-sync-service']['CREATED'] > 604800 ) {
            // If session was created > 1 week ago
            session_regenerate_id( true );
            $_SESSION['freya-sync-service']['CREATED'] = time();
            $this->updateSession();
        }
    }

    /**
     * Clears settings that need refetching on a new order
     */
    public function clearSessionAfterOrder(): void
    {
        $this->addressModalDismissed = false;
        $this->fetchedLocations      = [];
    }

    public function loadSession(): void
    {
        $account_storage = new AccountStore();

        $session = $_SESSION['freya-sync-service'] ?? [];

        if ( isset( $session['userAddress'] ) ) {
            $this->userAddress = $session['userAddress'];
        } elseif ( $account_storage->is_available() ) {
            $found_in_account = $account_storage->get( 'address' );

            if ( $found_in_account instanceof UserAddress ) {
                $this->userAddress = $found_in_account->displayName;
            }
        }

        $this->addressModalDismissed = $session['addressModalWasDismissed'] ?? false;

        if ( isset( $session['locationUid'] ) ) {
            $this->locationUid = $session['locationUid'];
        } elseif ( $account_storage->is_available() ) {
            $found_in_account = $account_storage->get( 'location_uid' );

            if ( is_string( $found_in_account ) ) {
                $this->locationUid = $found_in_account;
            } else {
                $this->locationUid = null;
                $account_storage->forget( 'location_uid' );
            }
        }

        if ( isset( $session['locationName'] ) ) {
            $this->locationName = $session['locationName'];
        } elseif ( $account_storage->is_available() ) {
            $found_in_account = $account_storage->get( 'location_name' );

            if ( is_string( $found_in_account ) ) {
                $this->locationName = $found_in_account;
            } else {
                $this->locationName = null;
                $account_storage->forget( 'location_name' );
            }
        }

        if ( isset( $session['isPickup'] ) ) {
            $this->isPickup = $session['isPickup'];
        } elseif ( $account_storage->is_available() ) {
            $found_in_account = $account_storage->get( 'pickup' );

            if ( null !== $found_in_account ) {
                $this->isPickup = (bool) $found_in_account;
            }
        }

        if ( isset( $session['userAddressInputs'] ) ) {
            $this->userAddressInputs = unserialize(
                $session['userAddressInputs'],
                [ 'allowed_classes' => [ UserAddressInputs::class ] ]
            );
        } elseif ( $account_storage->is_available() ) {
            $found_in_account = $account_storage->get( 'user_address_inputs' );

            if ( $found_in_account instanceof UserAddressInputs ) {
                $this->userAddressInputs = $found_in_account;
            } else {
                $this->userAddressInputs = null;
                $account_storage->forget( 'user_address_inputs' );
            }
        }

        if ( isset( $session['geo'] ) ) {
            $this->address = unserialize(
                $session['geo'],
                [ 'allowed_classes' => [ UserAddress::class ] ]
            );
        } elseif ( $account_storage->is_available() ) {
            $found_in_account = $account_storage->get( 'address' );

            if ( $found_in_account instanceof UserAddress ) {
                $this->address = $found_in_account;
            } else {
                $this->address = null;
                $account_storage->forget( 'address' );
            }
        }

        if ( isset( $session['tax'] ) ) {
            $this->tax = unserialize( $session['tax'] );
        } else {
            $this->tax = null;
        }

        if ( isset( $session['fetchedLocations-2'] ) ) {
            $this->fetchedLocations = unserialize( $session['fetchedLocations-2'] );
        }

        if ( isset( $session['notifications'] ) ) {
            $this->notifications = unserialize( $session['notifications'] );
        }
    }

    public static function get(): SessionService
    {
        if ( ! isset( static::$instance ) ) {
            static::$instance = new SessionService();
        }

        return static::$instance;
    }

    /**
     * Every time you call session_start(), PHP adds another
     * identical session cookie to the response header. Do this
     * enough times, and your response header becomes big enough
     * to choke the web server.
     *
     * This method clears out the duplicate session cookies. You can
     * call it after each time you've called session_start(), or call it
     * just before you send your headers.
     */
    private function clearDuplicateCookies(): void {
        // If headers have already been sent, there's nothing we can do
        if (headers_sent()) {
            return;
        }

        $cookies = array();
        foreach (headers_list() as $header) {
            // Identify cookie headers
            if (str_starts_with($header, 'Set-Cookie:')) {
                $cookies[] = $header;
            }
        }

        // Removes all cookie headers, including duplicates
        header_remove('Set-Cookie');

        // Restore one copy of each cookie
        foreach(array_unique($cookies) as $cookie) {
            header($cookie, false);
        }
    }
}
