
from subprocess import Popen, PIPE
from os.path import expanduser
from gi.repository import Gtk, Gdk

import json
import sys
import time
import os
import getpass
import argparse

GPG_ERR_PASSPHRASE = "Bad session key"

FILENAME  = "entries"
DIRECTORY = expanduser("~") + "/.sef"
FULLPATH  = DIRECTORY + "/" + FILENAME


class GPGException(Exception):
    pass


class Entries:

    def __init__(self, passphrase, file_path):
        self.passphrase = passphrase
        self.path = file_path

    def encrypt_msg(self, msg):
        cmd  = ["gpg",
                "--batch",
                "--yes",
                "--symmetric",
                "--passphrase", self.passphrase,
                "-o", self.path,
                "--cipher-algo", "AES256"]

        proc = Popen(cmd,
                     stdout = PIPE,
                     stderr = PIPE,
                     stdin  = PIPE,
                     shell  = False)

        proc.stdin.write(bytes(msg, "UTF-8"))
        return proc.communicate()

    def decrypt_msg(self):
        cmd  = ["gpg",
                "--batch",
                "--yes",
                "--decrypt",
                "--passphrase", self.passphrase,
                "--cipher-algo", "AES256",
                self.path]

        proc = Popen(cmd,
                     stdin  = PIPE,
                     stdout = PIPE,
                     stderr = PIPE,
                     shell  = False)

        return proc.communicate()

    def load(self):
        proc = self.decrypt_msg()
        out = proc[0].decode("UTF-8")
        err = proc[1].decode("UTF-8")

        if GPG_ERR_PASSPHRASE in err:
            raise GPGException("Bad passphrase")

        if not out:
            self.entries = {}
        else:
            try:
                self.entries = json.loads(out)
            except ValueError as e:
                print("File format corruption")

    def save(self):
        e_json = json.dumps(self.entries)
        proc = self.encrypt_msg(e_json)
        out = proc[0].decode("UTF-8")
        err = proc[1].decode("UTF-8")

        if GPG_ERR_PASSPHRASE in err:
            raise GPGException("Bad passphrase")

    def add(self, entry, entry_name):
        if entry_name in self.entries:
            return False

        self.entries[entry_name] = entry
        return True

    def get_names(self):
        entries = list()

        for n, v in self.entries.items():
            entries.append(n)
        return entries

    def get_names_by_tag(self, tag):
        names = list()

        for n, v in self.entries.items():
            if v["tag"] == tag:
                names.append(n)
        return names

    def get_tags(self, tag):
        tags = set()

        for entry in self.entries:
            tags.add(entry["tag"])
        return tags

    def get(self, entry):
        return self.entries[entry]

    def remove(self, entry):
        del self.entries[entry]

    def to_json(self):
        return json.dumps(self.entries)


def op_add(args):
    p1 = getpass.getpass("[Enter password]: ")
    p2 = getpass.getpass("[Re-Enter password]: ")
    p3 = getpass.getpass("[Enter Passphrase]: ")

    entry = {"password" : p1,
             "name"     : args.name,
             "tag"      : args.tag}

    if p1 != p2:
        print("Password missmatch!")
        return

    try:
        entries = Entries(p3, FULLPATH)
        entries.load()

        if entries.add(entry, args.entry):
            entries.save()
            print("New entry created.")
        else:
            print("The entry already exists!")
    except GPGException as e:
        print(e)


def op_list(args):
    passphrase = getpass.getpass("[Enter Passphrase]: ")

    try:
        entries = Entries(passphrase, FULLPATH)
        entries.load()
        if not args.tag:
            for n in entries.get_names():
                print(n)
        else:
            for n in entries.get_names_by_tag(args.tag):
                print(n)
    except GPGException as e:
        print(e)


def op_get_attribute(args, attribute):
    if not args.entry:
        print("No entry specified. Use \"-e [entry]\"")
        return

    passphrase = getpass.getpass("[Enter Passphrase]: ")

    try:
        entries = Entries(passphrase, FULLPATH)
        entries.load()
        a = entries.get(args.entry)[attribute]
        if args.stdout:
            print(a)
        else:
            clip = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
            clip.set_text(a, -1)
            clip.store()
    except GPGException as e:
        print(e)
    except KeyError as e:
        print("No such entry.")


def op_remove(args):
    if not args.entry:
        print("No entry specified. Use \"-e [entry]\"")
        return

    passphrase = getpass.getpass("[Enter Passphrase]: ")

    try:
        entries = Entries(passphrase, FULLPATH)
        entries.load()
        entries.remove(args.entry)
        entries.save()
        print("Entry removed.")
    except GPGException as e:
        print(e)
    except KeyError as e:
        print("No such entry.")


def op_export(args):
    passphrase = getpass.getpass("[Enter Passphrase]: ")

    try:
        entries = Entries(passphrase, FULLPATH)
        entries.load()
        print(entries.to_json())
    except GPGException as e:
        print(e)


def get_args_parser():
    parser = argparse.ArgumentParser(description="Password Util")

    parser.add_argument("operation",
                        help="Operations: add, remove, update, export")

    parser.add_argument("-e", "--entry", help="The entry")
    parser.add_argument("-n", "--name", help="Username")
    parser.add_argument("-t", "--tag", help="Entry tag", default="")
    parser.add_argument("-o", "--stdout", help="Print to stdout", action="store_true")

    return parser


def process_actions():
    parser = get_args_parser()
    args   = parser.parse_args()

    if args.operation == "export":
        op_export(args)

    elif args.operation == "add":
        op_add(args)

    elif args.operation == "remove":
        op_remove(args)

    elif args.operation == "list":
        op_list(args)