package com.atlassian.stash.internal.audit;

import com.atlassian.bitbucket.audit.AuditEntry;
import com.atlassian.bitbucket.audit.AuditEntryBuilder;
import com.atlassian.stash.internal.logback.LoggingConstants;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.user.ApplicationUser;
import org.apache.commons.lang3.StringUtils;
import org.jboss.logging.MDC;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.Date;

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class DefaultAuditEntryLoggingServiceTest {

    public static final String SOURCE_IP = "172.0.0.0";

    private DefaultAuditEntryLoggingService service;

    @Before
    public void setup() {
        service = new DefaultAuditEntryLoggingService(100);
        MDC.put(LoggingConstants.MDC_REQUEST_ID, "requestid");
        MDC.put(LoggingConstants.MDC_SESSION_ID, "sessionid");
    }

    @After
    public void tearDown() {
        MDC.remove(LoggingConstants.MDC_REQUEST_ID);
        MDC.remove(LoggingConstants.MDC_SESSION_ID);
    }

    @Test
    public void testLog() throws Exception {
        AuditEntry event = new AuditEntryBuilder()
                .action("action")
                .details("")
                .repository(mock(Repository.class))
                .target("target")
                .timestamp(new Date())
                .user(mockUser("tbright"))
                .build();
        service.log(event);
    }

    @Test
    public void testMessageCreationContent() throws Exception {
        Date timestamp = new Date();
        AuditEntry event = new AuditEntryBuilder()
                .action("AnAction")
                .details("details")
                .project(mock(Project.class))
                .repository(mock(Repository.class))
                .sourceIpAddress(SOURCE_IP)
                .target("target")
                .timestamp(timestamp)
                .user(mockUser("tbright"))
                .build();

        String message = service.getLogMessage(event);
        assertTrue(message.contains("tbright"));
        assertTrue(message.contains("AnAction"));
        assertTrue(message.contains("target"));
        assertTrue(message.contains(SOURCE_IP));
        assertTrue(message.contains("details"));
        assertTrue(message.contains("" + timestamp.getTime()));
        assertTrue(message.contains("requestid"));
        assertTrue(message.contains("sessionid"));
    }

    @Test
    public void testMessageCreationStructure() throws Exception {
        Date timestamp = new Date();
        AuditEntry event = new AuditEntryBuilder()
                .action("AnAction")
                .details("details")
                .project(mock(Project.class))
                .repository(mock(Repository.class))
                .sourceIpAddress(SOURCE_IP)
                .target("target")
                .timestamp(timestamp)
                .user(mockUser("tbright"))
                .build();

        String message = service.getLogMessage(event);
        assertEquals("172.0.0.0 | AnAction | tbright | " + timestamp.getTime() + " | target | details | requestid | sessionid", message);
    }

    @Test
    public void testMessageCreationMaxLength() throws Exception {
        String message = createMessage("tbright", StringUtils.repeat("a", 2000));
        assertTrue(message.endsWith("aaa... | requestid | sessionid"));
        assertTrue(message.length() < 200);
    }

    @Test
    public void testNewLinesEscaped() throws Exception {
        assertFalse(createMessage("\n").contains("\n"));
    }

    @Test
    public void testNewLinesEscapedNonEndOfLine() throws Exception {
        assertFalse(createMessage("\nabcde").contains("\n"));
    }

    @Test
    public void testNewLinesEscapedNonEndOfLineMultiple() throws Exception {
        assertFalse(createMessage("\nabcde\n\n\n\n\n").contains("\n"));
    }

    @Test
    public void testNewLinesReturnEscapedNonEndOfLine() throws Exception {
        assertFalse(createMessage("\rabcde").contains("\r"));
    }

    @Test
    public void testSpecialCharactersEscaped() throws Exception {
        assertTrue(createMessage(" | ").contains(" PIPE_CHAR "));
    }

    @SuppressWarnings("ConstantConditions")
    @Test(expected = NullPointerException.class)
    public void testLogNull() throws Exception {
        service.log(null);
    }

    private static ApplicationUser mockUser(String username) {
        ApplicationUser user = mock(ApplicationUser.class);
        when(user.getName()).thenReturn(username);

        return user;
    }

    private String createMessage(String username) {
        return createMessage(username, "details");
    }

    private String createMessage(String username, String details) {
        AuditEntry event = new AuditEntryBuilder()
                .action("AnAction")
                .details(details)
                .project(mock(Project.class))
                .repository(mock(Repository.class))
                .sourceIpAddress(SOURCE_IP)
                .target("target")
                .timestamp(new Date())
                .user(mockUser(username))
                .build();
        return service.getLogMessage(event);
    }
}
