/*
 * Copyright 2017, 2018 IBM Corp. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy
 * of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package com.ibm.etcd.client;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import com.google.protobuf.ByteString;
import com.ibm.etcd.api.LeaseGrantResponse;
import com.ibm.etcd.api.LockResponse;
import com.ibm.etcd.client.kv.KvClient;
import com.ibm.etcd.client.lease.LeaseClient;
import com.ibm.etcd.client.lease.PersistentLease;
import com.ibm.etcd.client.lock.LockClient;

import io.grpc.Deadline;
import io.grpc.Status.Code;
import io.grpc.StatusRuntimeException;

public class LockTest {

    static KvStoreClient client;

    static LockClient lockClient;
    static KvClient kvClient;
    static LeaseClient leaseClient;

    @BeforeClass
    public static void setup() {
        client = EtcdClient.forEndpoint("localhost", 2379)
                .withPlainText().build();
        lockClient = client.getLockClient();
        kvClient = client.getKvClient();
        leaseClient = client.getLeaseClient();
    }
    @AfterClass
    public static void teardown() throws Exception {
        if (client != null) {
            client.close();
        }
    }

    @Test
    public void testWithSessionLease() throws Exception {
        LockResponse lr = lockClient.lock(KeyUtils.bs("mylock"))
                .deadline(Deadline.after(500, TimeUnit.MILLISECONDS)).sync();

        ByteString lockKey = lr.getKey();
        assertNotNull(lockKey);
        assertTrue(kvClient.txnIf().exists(lockKey).sync().getSucceeded());

        assertNotNull(lockClient.unlock(lockKey).sync());
        assertFalse(kvClient.txnIf().exists(lockKey).sync().getSucceeded());
    }

    @Test
    public void testWithLeaseId() throws Exception {
        LeaseGrantResponse lgr = leaseClient.grant(3L).sync();

        LockResponse lr = lockClient.lock(KeyUtils.bs("mylock2")).withLease(lgr.getID()).sync();

        ByteString lockKey = lr.getKey();
        assertNotNull(lockKey);
        assertTrue(kvClient.txnIf().exists(lockKey).sync().getSucceeded());

        assertNotNull(lockClient.unlock(lockKey).sync());
        assertFalse(kvClient.txnIf().exists(lockKey).sync().getSucceeded());
    }

    @Test
    public void testWithPersistentLease() throws Exception {
        LeaseClient lec = client.getLeaseClient();

        PersistentLease pl = lec.maintain().start();

        LockResponse lr = lockClient.lock(KeyUtils.bs("mylock3")).withLease(pl).sync();

        ByteString lockKey = lr.getKey();
        assertNotNull(lockKey);
        assertTrue(kvClient.txnIf().exists(lockKey).sync().getSucceeded());

        pl.close();

        Thread.sleep(500L); // don't currently have future for PL close unfortunately

        assertFalse(kvClient.txnIf().exists(lockKey).sync().getSucceeded());
    }

    @Test
    public void testWithContention() throws Exception {
        ByteString lockName = KeyUtils.bs(UUID.randomUUID().toString());

        LeaseGrantResponse lgr1 = leaseClient.grant(120L).sync(), lgr2 = leaseClient.grant(120L).sync();

        // lock with first lease
        LockResponse lr = lockClient.lock(lockName).withLease(lgr1.getID()).sync();

        // attempt lock with second one, should timeout
        long nanosBefore = System.nanoTime();
        try {
            LockResponse lr2 = lockClient.lock(lockName).withLease(lgr2.getID())
                    .deadline(Deadline.after(100, TimeUnit.MILLISECONDS)).sync();
            fail("second lock attempt should timeout");
        } catch (StatusRuntimeException sre) {
            assertEquals(Code.DEADLINE_EXCEEDED, sre.getStatus().getCode());
        }
        long tookMs = (System.nanoTime() - nanosBefore) / 1000_000L;
        assertTrue(tookMs > 50 && tookMs < 150);
    }

}