package net.ranides.assira.credentials;

import net.ranides.assira.collection.maps.*;
import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.collection.query.CQueryBuilder;
import net.ranides.assira.collection.sets.SetUtils;
import net.ranides.assira.generic.TypeToken;
import net.ranides.assira.reflection.IClass;
import org.linguafranca.pwdb.*;
import org.linguafranca.pwdb.kdbx.KdbxCreds;
import org.linguafranca.pwdb.kdbx.jaxb.JaxbDatabase;

import java.io.InputStream;
import java.net.URI;
import java.util.*;
import java.util.HashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author msieron
 */
public class PWDBStore implements UserStore {

    private final Database<? extends Database,? extends Group, ? extends Entry, ? extends Icon> db;

    public PWDBStore(InputStream db, byte[] password) {
        this(JaxbDatabase.load(new KdbxCreds(password), db));

    }

    public PWDBStore(Database db) {
        this.db = db;
    }

    @Override
    public CQuery<GenericMap<String>> match(URI uri) {
        String uristr = uri.toString();
        List<GenericMap<String>> list = db
            .findEntries(m -> m.matchUrl(uristr))
            .stream()
            .map(PWDMap::new)
            .collect(Collectors.toList());
        return CQueryBuilder.fromCollection(list);
    }

    @Override
    public CQuery<GenericMap<String>> find(String user) {
        List<GenericMap<String>> list = db
            .findEntries(m -> user.equals(m.getTitle()))
            .stream()
            .map(PWDMap::new)
            .collect(Collectors.toList());
        return CQueryBuilder.fromCollection(list);
    }

    private static class PWDMap extends AGenericMap<String> {

        private static Map<GenericKey<String, ?>, Function<Entry,?>> PWM = new HashMap<>();

        static {
            IClass<Entry> im = IClass.typeinfo(Entry.class);

            PWM.put(UserProperty.LOGIN,     getMethod(im,"getUsername"));
            PWM.put(UserProperty.PASSWORD,  getMethod(im,"getPassword"));
            PWM.put(UserProperty.ACCOUNT,   getMethod(im,"getTitle"));
            PWM.put(UserProperty.URI,       getPredicateURI(im));
        }

        private final Entry value;

        public PWDMap(Entry value) {
            this.value = value;
        }

        @Override
        public int size() {
            return 4;
        }

        @Override
        public boolean containsKey(GenericKey<String, ?> key) {
            return PWM.containsKey(key);
        }

        @Override
        public <V> V get(GenericKey<String, V> key) {
            Function<Entry, ?> m = PWM.get(key);
            return m==null ? null : key.type().cast(m.apply(value));
        }

        @Override
        public Set<GenericEntry<String>> entrySet() {
            return SetUtils.map(PWM.keySet(), m -> new GenericEntry.Simple<>(m,PWM.get(m).apply(value)));
        }

        private static Function<Entry, Object> getMethod(IClass<Entry> im, String name) {
            return im.method(name).get()::apply;
        }

        private static Function<Entry, Predicate<URI>> getPredicateURI(IClass<Entry> im) {
            Function<Object,String> getURL = im.method("getUrl").get().handle(new TypeToken<Function<Object, String>>(){});
            return e -> (u -> u.toString().contains(getURL.apply(e)));
        }
    }
}
