package net.lingala.zip4j.util;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.spi.FileSystemProvider;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

public class FileUtilsTestLinuxAndMac {

  private static final String ACTUAL_OS = System.getProperty("os.name");

  @Before
  public void setup() {
    System.setProperty("os.name", "linux");
  }

  @After
  public void cleanup() {
    System.setProperty("os.name", ACTUAL_OS);
  }

  @Test
  public void testSetFileAttributesWhenAttributesIsNullDoesNothing() {
    FileUtils.setFileAttributes(mock(Path.class), null);
  }

  @Test
  public void testSetFileAttributesWhenAttributesIsEmptyDoesNothing() {
    FileUtils.setFileAttributes(mock(Path.class), new byte[0]);
  }

  @Test
  public void testSetFileAttributesWhenNoAttributesSetDoesNothing() throws IOException {
    Path path = mock(Path.class);
    PosixFileAttributeView posixFileAttributeView = mockPosixFileAttributeView(path, false);

    FileUtils.setFileAttributes(path, new byte[4]);

    verifyZeroInteractions(posixFileAttributeView);
  }

  @Test
  public void testSetFileAttributesWhenAttributesSet() throws IOException {
    Path path = mock(Path.class);
    PosixFileAttributeView posixFileAttributeView = mockPosixFileAttributeView(path, false);

    byte fourthByte = 1; // Only first bit is set
    byte thirdByte = -1; // All bits are set

    FileUtils.setFileAttributes(path, new byte[]{0, 0, thirdByte, fourthByte});

    ArgumentCaptor<Set> permissionsCaptor = ArgumentCaptor.forClass(Set.class);
    verify(posixFileAttributeView).setPermissions(permissionsCaptor.capture());
    verifyAllPermissionSet(permissionsCaptor.getValue());
  }

  @Test
  public void testGetFileAttributesWhenFileIsNullReturnsEmptyBytes() {
    byte[] attributes = FileUtils.getFileAttributes(null);
    assertThat(attributes).contains(0, 0, 0, 0);
  }

  @Test
  public void testGetFileAttributesAsDefinedForRegularFile() throws IOException {
    testGetFileAttributesGetsAsDefined(false);
  }

  @Test
  public void testGetFileAttributesAsDefinedForDirectory() throws IOException {
    testGetFileAttributesGetsAsDefined(true);
  }

  @Test
  public void testIsWindowsReturnsFalse() {
    assertThat(FileUtils.isWindows()).isFalse();
  }

  private void testGetFileAttributesGetsAsDefined(boolean isDirectory) throws IOException {
    File file = mock(File.class);
    Path path = mock(Path.class);
    when(file.toPath()).thenReturn(path);
    when(file.exists()).thenReturn(true);
    PosixFileAttributeView posixFileAttributeView = mockPosixFileAttributeView(path, isDirectory);
    PosixFileAttributes posixFileAttributes = mock(PosixFileAttributes.class);
    Set<PosixFilePermission> posixFilePermissions = getAllPermissions();
    when(posixFileAttributes.permissions()).thenReturn(posixFilePermissions);
    when(posixFileAttributeView.readAttributes()).thenReturn(posixFileAttributes);

    byte[] fileAttributes = FileUtils.getFileAttributes(file);

    assertThat(fileAttributes).hasSize(4);
    assertThat(fileAttributes[0]).isEqualTo((byte) 0);
    assertThat(fileAttributes[1]).isEqualTo((byte) 0);
    assertThat(fileAttributes[2]).isEqualTo((byte) -1);

    if (isDirectory) {
      assertThat(fileAttributes[3]).isEqualTo((byte) 65);
    } else {
      assertThat(fileAttributes[3]).isEqualTo((byte) -127);
    }
  }

  private Set<PosixFilePermission> getAllPermissions() {
    return Stream.of(
        PosixFilePermission.OWNER_READ,
        PosixFilePermission.OWNER_WRITE,
        PosixFilePermission.OWNER_EXECUTE,
        PosixFilePermission.GROUP_READ,
        PosixFilePermission.GROUP_WRITE,
        PosixFilePermission.GROUP_EXECUTE,
        PosixFilePermission.OTHERS_READ,
        PosixFilePermission.OTHERS_WRITE,
        PosixFilePermission.OTHERS_EXECUTE
    ).collect(Collectors.toSet());
  }
  
  private void verifyAllPermissionSet(Set<PosixFilePermission> permissions) {
    assertThat(permissions).contains(
        PosixFilePermission.OWNER_READ,
        PosixFilePermission.OWNER_WRITE,
        PosixFilePermission.OWNER_EXECUTE,
        PosixFilePermission.GROUP_READ,
        PosixFilePermission.GROUP_WRITE,
        PosixFilePermission.GROUP_EXECUTE,
        PosixFilePermission.OTHERS_READ,
        PosixFilePermission.OTHERS_WRITE,
        PosixFilePermission.OTHERS_EXECUTE
    );
  }

  private PosixFileAttributeView mockPosixFileAttributeView(Path path, boolean isDirectory) throws IOException {
    FileSystemProvider fileSystemProvider = mock(FileSystemProvider.class);
    FileSystem fileSystem = mock(FileSystem.class);
    PosixFileAttributeView posixFileAttributeView = mock(PosixFileAttributeView.class);

    when(path.getFileSystem()).thenReturn(fileSystem);
    when(fileSystemProvider.getFileAttributeView(path, PosixFileAttributeView.class)).thenReturn(posixFileAttributeView);
    when(fileSystemProvider.getFileAttributeView(path, PosixFileAttributeView.class, LinkOption.NOFOLLOW_LINKS))
        .thenReturn(posixFileAttributeView);
    when(path.getFileSystem().provider()).thenReturn(fileSystemProvider);

    mockRegularFileOrDirectory(fileSystemProvider, path, isDirectory);

    return posixFileAttributeView;
  }

  private void mockRegularFileOrDirectory(FileSystemProvider fileSystemProvider, Path path, boolean isDirectory)
      throws IOException {
    BasicFileAttributes basicFileAttributes = mock(BasicFileAttributes.class);
    when(basicFileAttributes.isRegularFile()).thenReturn(!isDirectory);
    when(basicFileAttributes.isDirectory()).thenReturn(isDirectory);
    when(fileSystemProvider.readAttributes(path, BasicFileAttributes.class)).thenReturn(basicFileAttributes);
    when(fileSystemProvider.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS))
        .thenReturn(basicFileAttributes);
  }
}