package com.wesabe.grendel.entities.tests;

import static org.fest.assertions.Assertions.*;
import static org.mockito.Mockito.*;

import java.io.FileInputStream;
import java.lang.reflect.Field;
import java.security.SecureRandom;

import javax.ws.rs.core.MediaType;

import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils;
import org.joda.time.DateTimeZone;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;

import com.google.common.collect.ImmutableList;
import com.wesabe.grendel.entities.Document;
import com.wesabe.grendel.entities.User;
import com.wesabe.grendel.openpgp.KeySet;
import com.wesabe.grendel.openpgp.MessageReader;
import com.wesabe.grendel.openpgp.MessageWriter;

@RunWith(Enclosed.class)
public class DocumentTest {
	public static class A_Fresh_Document {
		private User owner;
		private String name;
		private DateTime now;
		
		@Before
		public void setup() throws Exception {
			this.now = new DateTime(2009, 12, 27, 10, 0, 43, 0, DateTimeZone.UTC);
			DateTimeUtils.setCurrentMillisFixed(now.getMillis());

			this.owner = mock(User.class);
			this.name = "sekrit";
		}
		
		@After
		public void teardown() {
			DateTimeUtils.setCurrentMillisSystem();
		}
		
		@Test
		public void itHasAnOwner() throws Exception {
			final Document doc = new Document(owner, name, MediaType.APPLICATION_OCTET_STREAM_TYPE);
			
			assertThat(doc.getOwner()).isEqualTo(owner);
		}
		
		@Test
		public void itHasAName() throws Exception {
			final Document doc = new Document(owner, name, MediaType.APPLICATION_OCTET_STREAM_TYPE);
			
			assertThat(doc.getName()).isEqualTo(name);
		}
		
		@Test
		public void itHasAModificationTimestamp() throws Exception {
			final Document doc = new Document(owner, name, MediaType.APPLICATION_OCTET_STREAM_TYPE);
			
			assertThat(doc.getModifiedAt()).isEqualTo(now);
		}
		
		@Test
		public void itHasACreationTimestamp() throws Exception {
			final Document doc = new Document(owner, name, MediaType.APPLICATION_OCTET_STREAM_TYPE);
			
			assertThat(doc.getCreatedAt()).isEqualTo(now);
		}
		
		@Test
		public void itHasAContentType() throws Exception {
			final Document doc = new Document(owner, name, MediaType.APPLICATION_OCTET_STREAM_TYPE);
			
			assertThat(doc.getContentType()).isEqualTo(MediaType.APPLICATION_OCTET_STREAM_TYPE);
		}
		
		@Test
		public void itIsLinkable() throws Exception {
			final Document doc = new Document(owner, name, MediaType.APPLICATION_OCTET_STREAM_TYPE);
			
			assertThat(doc.toString()).isEqualTo(name);
		}
		
		@Test
		public void itHasAnETag() throws Exception {
			final Document doc = new Document(owner, name, MediaType.APPLICATION_OCTET_STREAM_TYPE);
			
			assertThat(doc.getEtag()).isEqualTo("doc-document1.txt-0");
		}
	}
	
	public static class Encrypting_A_Document_Body {
		private KeySet ownerKeySet, recipientKeySet;
		private User owner, reader;
		private Document doc;
		
		@Before
		public void setup() throws Exception {
			final FileInputStream ownerKeyring = new FileInputStream("src/test/resources/secret-keyring.gpg");
			this.ownerKeySet = KeySet.load(ownerKeyring);
			ownerKeyring.close();
			
			final FileInputStream recipientKeyring = new FileInputStream("src/test/resources/another-secret-keyring.gpg");
			this.recipientKeySet = KeySet.load(recipientKeyring);
			recipientKeyring.close();
			
			this.owner = new User(ownerKeySet);
			this.reader = new User(recipientKeySet);
			this.doc = new Document(owner, "test", MediaType.APPLICATION_OCTET_STREAM_TYPE);
		}
		
		@Test
		public void itStoresItAsAnEncryptedOpenPGPMessage() throws Exception {
			final byte[] originalBody = "I am a secret document".getBytes();
			
			doc.linkUser(reader);
			doc.encryptAndSetBody(ownerKeySet.unlock("test".toCharArray()), new SecureRandom(), originalBody);

			final Field bodyField = doc.getClass().getDeclaredField("body");
			bodyField.setAccessible(true);

			final byte[] encryptedBody = (byte[]) bodyField.get(doc);
			final MessageReader reader = new MessageReader(ownerKeySet, recipientKeySet.unlock("test2".toCharArray()));

			final byte[] decryptedBody = reader.read(encryptedBody);
			assertThat(decryptedBody).isEqualTo(originalBody);
		}
	}

	public static class Decrypting_A_Document_Body {
		private KeySet ownerKeySet, recipientKeySet;
		private User owner;
		private Document doc;

		@Before
		public void setup() throws Exception {
			final FileInputStream ownerKeyring = new FileInputStream("src/test/resources/secret-keyring.gpg");
			this.ownerKeySet = KeySet.load(ownerKeyring);
			ownerKeyring.close();

			final FileInputStream recipientKeyring = new FileInputStream("src/test/resources/another-secret-keyring.gpg");
			this.recipientKeySet = KeySet.load(recipientKeyring);
			recipientKeyring.close();

			this.owner = new User(ownerKeySet);
			this.doc = new Document(owner, "test", MediaType.APPLICATION_OCTET_STREAM_TYPE);
		}

		@Test
		public void itCanDecryptIt() throws Exception {
			final byte[] originalBody = "I am a secret document".getBytes();
			
			final MessageWriter writer = new MessageWriter(ownerKeySet.unlock("test".toCharArray()), ImmutableList.of(recipientKeySet), new SecureRandom());
			final byte[] encryptedBody = writer.write(originalBody);
			
			final Field bodyField = doc.getClass().getDeclaredField("body");
			bodyField.setAccessible(true);
			bodyField.set(doc, encryptedBody);
			
			final byte[] decryptedBody = doc.decryptBody(recipientKeySet.unlock("test2".toCharArray()));
			assertThat(decryptedBody).isEqualTo(originalBody);
		}
	}
	
	public static class Linking_A_User {
		private User user;
		private Document document;
		
		@SuppressWarnings("deprecation")
		@Before
		public void setup() throws Exception {
			this.user = new User();
			this.document = new Document();
			
			document.linkUser(user);
		}
		
		@Test
		public void itAddsTheDocumentToTheUsersLinkedDocuments() throws Exception {
			assertThat(user.getLinkedDocuments()).contains(document);
		}
		
		@Test
		public void itAddsTheUserToTheDocumentsLinkedUsers() throws Exception {
			assertThat(document.getLinkedUsers()).contains(user);
		}
		
		@Test
		public void itIsLinkedToTheUser() throws Exception {
			assertThat(document.isLinked(user)).isTrue();
		}
	}
	
	public static class Unlinking_A_User {
		private User user;
		private Document document;
		
		@SuppressWarnings("deprecation")
		@Before
		public void setup() throws Exception {
			this.user = new User();
			this.document = new Document();
			
			document.linkUser(user);
			
			document.unlinkUser(user);
		}
		
		@Test
		public void itAddsTheDocumentToTheUsersLinkedDocuments() throws Exception {
			assertThat(user.getLinkedDocuments()).excludes(document);
		}
		
		@Test
		public void itAddsTheUserToTheDocumentsLinkedUsers() throws Exception {
			assertThat(document.getLinkedUsers()).excludes(user);
		}
		
		@Test
		public void itIsNotLinkedToTheUser() throws Exception {
			assertThat(document.isLinked(user)).isFalse();
		}
	}
}

@@ -1192,13 +1192,20 @@
  = %22
-sekri
+document1.tx
 t%22;%0A

Fix Document's test.