import { h, Component } from 'preact'
import htm from 'htm'
import Crypto from 'crypto'
const html = htm.bind(h)

export class Keys extends Component {
	constructor(props) {//{{{
		super(props)
		this.state = {
			create: false,
		}

		props.nodeui.retrieveKeys()
	}//}}}
	render({ nodeui }, { create }) {//{{{
		let keys = nodeui.keys.value
			.sort((a,b)=>{
				if(a.description < b.description) return -1
				if(a.description > b.description) return  1
				return 0
			})
			.map(key=>
				html`<${KeyComponent} key=${`key-${key.ID}`} model=${key} />`
			)

		let createButton = ''
		let createComponents = ''
		if(create) {
			createComponents = html`
			<div id="key-create">
				<h2>New key</h2>

				<div class="fields">
					<input type="text" id="key-description" placeholder="Name" />

					<input type="password" id="key-pass1" placeholder="Password" />
					<input type="password" id="key-pass2" placeholder="Repeat password" />

					<textarea id="key-key" placeholder="Key"></textarea>

					<div>
						<button class="generate" onclick=${()=>this.generateKey()}>Generate</button>
						<button class="create" onclick=${()=>this.createKey()}>Create</button>
					</div>
				</div>
			</div>
			`
		} else {
			createButton = html`<div style="margin-top: 16px;"><button onclick=${()=>this.setState({ create: true })}>Create new key</button></div>`
		}

		return html`
			<div id="keys">
				<h1>Encryption keys</h1>
				<p>
					Unlock a key by clicking its name. Lock it by clicking it again.
				</p>

				<p>
					Copy the key and store it in a very secure place to have a way to access notes
					in case the password is forgotten, or database is corrupted.
				</p>

				<p>Click "View key" after unlocking it.</p>

				${createButton}
				${createComponents}

				<h2>Keys</h2>
				<div class="key-list">
					${keys}
				</div>
			</div>`
	}//}}}

	generateKey() {//{{{
		let keyTextarea = document.getElementById('key-key')
		let key = sjcl.codec.hex.fromBits(Crypto.generate_key()).replace(/(....)/g, '$1 ').trim()
		keyTextarea.value = key
	}//}}}
	validateNewKey() {//{{{
		let keyDescription = document.getElementById('key-description').value
		let keyTextarea = document.getElementById('key-key').value
		let pass1 = document.getElementById('key-pass1').value
		let pass2 = document.getElementById('key-pass2').value

		if(keyDescription.trim() == '')
			throw new Error('The key has to have a description')

		if(pass1.trim() == '' || pass1.length < 4)
			throw new Error('The password has to be at least 4 characters long.')

		if(pass1 != pass2)
			throw new Error(`Passwords doesn't match`)

		let cleanKey = keyTextarea.replace(/\s+/g, '')
		if(!cleanKey.match(/^[0-9a-f]{64}$/i))
			throw new Error('Invalid key - has to be 64 characters of 0-9 and A-F')
	}//}}}
	createKey() {//{{{
		try {
			this.validateNewKey()

			let description = document.getElementById('key-description').value
			let keyAscii = document.getElementById('key-key').value
			let pass1 = document.getElementById('key-pass1').value

			// Key in hex taken from user.
			let actual_key = sjcl.codec.hex.toBits(keyAscii.replace(/\s+/g, ''))

			// Key generated from password, used to encrypt the actual key.
			let pass_gen = Crypto.pass_to_key(pass1)

			let crypto = new Crypto(pass_gen.key)
			let encrypted_actual_key = crypto.encrypt(actual_key, 0x1n, false)

			// Database value is salt + actual key, needed to generate the same key from the password.
			let db_encoded = sjcl.codec.hex.fromBits(
				pass_gen.salt.concat(encrypted_actual_key)
			)

			// Create on server.
			window._app.current.request('/key/create', {
				description,
				key: db_encoded,
			})
			.then(res=>{
				let key = new Key(res.Key, this.props.nodeui.keyCounter)
				this.props.nodeui.keys.value = this.props.nodeui.keys.value.concat(key)
			})
			.catch(window._app.current.responseError)
		} catch(err) {
			alert(err.message)
			return
		}
	}//}}}
}

export class Key {
	constructor(data, counter_callback) {//{{{
		this.ID           = data.ID
		this.description  = data.Description
		this.encryptedKey = data.Key
		this.key          = null

		this._counter_cbk = counter_callback

		let hex_key = window.sessionStorage.getItem(`key-${this.ID}`)
		if(hex_key)
			this.key = sjcl.codec.hex.toBits(hex_key)
	}//}}}
	status() {//{{{
		if(this.key === null)
			return 'locked'
		return 'unlocked'
	}//}}}
	lock() {//{{{
		this.key = null
		window.sessionStorage.removeItem(`key-${this.ID}`)
	}//}}}
	unlock(password) {//{{{
		let db = sjcl.codec.hex.toBits(this.encryptedKey)
		let salt = db.slice(0, 4)
		let pass_key = Crypto.pass_to_key(password, salt)
		let crypto = new Crypto(pass_key.key)
		this.key = crypto.decrypt(sjcl.codec.base64.fromBits(db.slice(4)))
		window.sessionStorage.setItem(`key-${this.ID}`, sjcl.codec.hex.fromBits(this.key))
	}//}}}
	async counter() {//{{{
		return this._counter_cbk()
	}//}}}
}

export class KeyComponent extends Component {
	constructor({ model }) {//{{{
		super({ model })
		this.state = {
			show_key: false,
		}
	}//}}}
	render({ model }, { show_key }) {//{{{
		let status = ''
		switch(model.status()) {
			case 'locked':
				status = html`<div class="status locked"><img src="/images/${window._VERSION}/padlock-closed.svg" /></div>`
				break

			case 'unlocked':
				status = html`<div class="status unlocked"><img src="/images/${window._VERSION}/padlock-open.svg" /></div>`
				break
		}

		let hex_key = ''
		if(show_key) {
			if(model.status() == 'locked')
				hex_key = html`<div class="hex-key">Unlock key first</div>`
			else {
				let key = sjcl.codec.hex.fromBits(model.key)
				key = key.replace(/(....)/g, "$1 ").trim()
				hex_key = html`<div class="hex-key">${key}</div>`
			}
		}

		let unlocked = model.status()=='unlocked'

		return html`
			<div class="status" onclick=${()=>this.toggle()}>${status}</div>
			<div class="description" onclick=${()=>this.toggle()}>${model.description}</div>
			<div class="view" onclick=${()=>this.toggleViewKey()}>${unlocked ? 'View key' : ''}</div>
			${hex_key}
		`
	}//}}}
	toggle() {//{{{
		if(this.props.model.status() == 'locked')
			this.unlock()
		else
			this.lock()
	}//}}}
	lock() {//{{{
		this.props.model.lock()
		this.forceUpdate()
	}//}}}
	unlock() {//{{{
		let pass = prompt("Password")
		if(!pass)
			return

		try {
			this.props.model.unlock(pass)
			this.forceUpdate()
		} catch(err) {
			alert(err)
		}
	}//}}}
	toggleViewKey() {//{{{
		this.setState({ show_key: !this.state.show_key })
	}//}}}
}

// vim: foldmethod=marker
