#include "delete_cookie.h"

#include <balancer/kernel/cookie/domain/domain.h>
#include <balancer/kernel/cookie/utils/utils.h>

namespace NSrvKernel {

    TStringBuf ParentPath(TStringBuf path) noexcept {
        const auto pos = path.rfind('/');
        if (pos == TStringBuf::npos) {
            return path;
        }
        if (pos + 1 == path.size()) {
            return path.size() > 1 ? path.SubStr(0, path.size() - 1) : path;
        }
        return path.SubStr(0, pos + 1);
    }

    TSetCookie DeleteCookieLocal(const TDelCookieCtx& ctx) noexcept {
        TSetCookie res;
        res.Name = ctx.Name;
        res.Path = ctx.Path;
        if (ctx.Host) {
            res.Domain.append(TStringBuf(".")).append(NCookie::SkipDot(ctx.Host));
        }
        res.Expires = TExpires::Past();
        res.MaxAge = 0;
        res.Secure = ctx.Secure;
        return res;
    }

    void DeleteCookieEveryPath(TVector<TSetCookie>& res, const TDelCookieCtx& ctx, bool keepGlobal) noexcept {
        if (!ctx.Path || !ctx.Path.StartsWith('/')) {
            res.emplace_back(DeleteCookieLocal(ctx.SetPath({})));
            return;
        }

        size_t last = 0; // always has been
        while (last != TStringBuf::npos) {
            auto lastPath = ctx.Path.SubStr(0, last + 1);
            if (!(keepGlobal && lastPath == "/"sv)) {
                res.emplace_back(DeleteCookieLocal(ctx.SetPath(lastPath)));
            }
            last = ctx.Path.find('/', last + 1);
            auto nextPath = ctx.Path.SubStr(0, last);
            if (nextPath != lastPath) {
                res.emplace_back(DeleteCookieLocal(ctx.SetPath(nextPath)));
            }
        }
    }

    TVector<TSetCookie> DeleteCookieEveryPath(const TDelCookieCtx& ctx, bool keepGlobal) noexcept {
        TVector<TSetCookie> res;
        DeleteCookieEveryPath(res, ctx, keepGlobal);
        return res;
    }

    namespace {
        void DeleteCookieEveryDomainImpl(
            TVector<TSetCookie>& res, const TDelCookieCtx& ctx, bool keepGlobal, bool everyPath) noexcept
        {
            // A host-only cookie. Is not matched by domain cookies.
            if (everyPath) {
                DeleteCookieEveryPath(res, ctx.SetHost({}), false);
            } else {
                res.emplace_back(DeleteCookieLocal(ctx.SetHost({})));
            }

            TStringBuf domain = NCookie::SkipDot(ctx.Host);
            if (!ValidDomainName(domain)) {
                return;
            }
            const auto privSuff = ctx.Root ? ctx.Root : ShortestPrivateSuffix(ctx.Host);
            if (!privSuff.GetOrElse({})) {
                return;
            }
            while (domain.size() >= privSuff->size()) {
                if (everyPath) {
                    DeleteCookieEveryPath(res, ctx.SetHost(domain), domain == privSuff && keepGlobal);
                } else if (!(domain == privSuff && keepGlobal && ctx.Path == "/"sv)) {
                    res.emplace_back(DeleteCookieLocal(ctx.SetHost(domain)));
                }
                domain.NextTok('.');
            }
        }
    }

    void DeleteCookieEveryDomain(TVector<TSetCookie>& res, const TDelCookieCtx& ctx, bool keepGlobal) noexcept {
        DeleteCookieEveryDomainImpl(res, ctx, keepGlobal, false);
    }

    TVector<TSetCookie> DeleteCookieEveryDomain(const TDelCookieCtx& ctx, bool keepGlobal) noexcept {
        TVector<TSetCookie> res;
        DeleteCookieEveryDomain(res, ctx, keepGlobal);
        return res;
    }

    void DeleteCookieEveryDomainPath(TVector<TSetCookie>& res, const TDelCookieCtx& ctx, bool keepGlobal) noexcept {
        DeleteCookieEveryDomainImpl(res, ctx, keepGlobal, true);
    }

    TVector<TSetCookie> DeleteCookieEveryDomainPath(const TDelCookieCtx& ctx, bool keepGlobal) noexcept {
        TVector<TSetCookie> res;
        DeleteCookieEveryDomainPath(res, ctx, keepGlobal);
        return res;
    }
}
