package com.etesync.syncadapter.model;

import com.etesync.journalmanager.model.SyncEntry;

import java.util.List;

import io.requery.Column;
import io.requery.Convert;
import io.requery.Converter;
import io.requery.Entity;
import io.requery.ForeignKey;
import io.requery.Generated;
import io.requery.Index;
import io.requery.Key;
import io.requery.ManyToOne;
import io.requery.OneToOne;
import io.requery.PostLoad;
import io.requery.ReferentialAction;
import io.requery.Table;

public class JournalModel {
    @Entity
    @Table(name = "Journal", uniqueIndexes = "journal_unique_together")
    public static abstract class Journal {
        @Key
        @Generated
        int id;

        @Index(value = "journal_unique_together")
        @Column(length = 64, nullable = false)
        String uid;

        @Convert(CollectionInfoConverter.class)
        CollectionInfo info;

        String owner;

        byte[] encryptedKey;

        @Column(length = 64)
        String remoteLastUid;

        @Deprecated
        long service;

        @Index(value = "journal_unique_together")
        @ForeignKey(update = ReferentialAction.CASCADE)
        @ManyToOne
        Service serviceModel;

        boolean deleted;

        @Column(value = "false")
        boolean readOnly;

        @PostLoad
        void afterLoad() {
            this.info.setServiceID(this.serviceModel.id);
            this.info.setUid(uid);
        }

        public Journal() {
            this.deleted = false;
        }

        public Journal(MyEntityDataStore data, CollectionInfo info) {
            this();
            this.info = info;
            this.uid = info.getUid();
            this.serviceModel = info.getServiceEntity(data);
        }

        public static List<JournalEntity> getJournals(MyEntityDataStore data, ServiceEntity serviceEntity) {
            List<JournalEntity> ret = data.select(JournalEntity.class).where(JournalEntity.SERVICE_MODEL.eq(serviceEntity).and(JournalEntity.DELETED.eq(false))).get().toList();
            for (JournalEntity journal : ret) {
                // FIXME: For some reason this isn't always being called, manually do it here.
                journal.afterLoad();
            }
            return ret;
        }

        public static JournalEntity fetch(MyEntityDataStore data, ServiceEntity serviceEntity, String uid) {
            JournalEntity ret = data.select(JournalEntity.class).where(JournalEntity.SERVICE_MODEL.eq(serviceEntity).and(JournalEntity.UID.eq(uid))).limit(1).get().firstOrNull();
            if (ret != null) {
                // FIXME: For some reason this isn't always being called, manually do it here.
                ret.afterLoad();
            }
            return ret;
        }

        public static JournalEntity fetchOrCreate(MyEntityDataStore data, CollectionInfo collection) {
            JournalEntity journalEntity = fetch(data, collection.getServiceEntity(data), collection.getUid());
            if (journalEntity == null) {
                journalEntity = new JournalEntity(data, collection);
            } else {
                journalEntity.setInfo(collection);
            }
            return journalEntity;
        }

        public String getLastUid(MyEntityDataStore data) {
            EntryEntity last = data.select(EntryEntity.class).where(EntryEntity.JOURNAL.eq(this)).orderBy(EntryEntity.ID.desc()).limit(1).get().firstOrNull();
            if (last != null) {
                return last.getUid();
            }

            return null;
        }

        public boolean isOwner(String username) {
            return (owner == null) || owner.equalsIgnoreCase(username);
        }
    }

    @Entity
    @Table(name = "Entry", uniqueIndexes = "entry_unique_together")
    public static abstract class Entry {
        @Key
        @Generated
        int id;

        @Index("entry_unique_together")
        @Column(length = 64, nullable = false)
        String uid;

        @Convert(SyncEntryConverter.class)
        SyncEntry content;

        @Index("entry_unique_together")
        @ForeignKey(update = ReferentialAction.CASCADE)
        @ManyToOne
        Journal journal;
    }


    @Entity
    @Table(name = "Service", uniqueIndexes = "service_unique_together")
    public static abstract class Service {
        @Key
        @Generated
        int id;

        @Index(value = "service_unique_together")
        @Column(nullable = false)
        String account;

        @Index(value = "service_unique_together")
        CollectionInfo.Type type;

        public static ServiceEntity fetchOrCreate(MyEntityDataStore data, String account, CollectionInfo.Type type) {
            ServiceEntity service = data.select(ServiceEntity.class).where(ServiceEntity.ACCOUNT.eq(account).and(ServiceEntity.TYPE.eq(type))).limit(1).get().firstOrNull();
            if (service == null) {
                // If our first time, create service and a journal
                ServiceEntity serviceEntity = new ServiceEntity();
                serviceEntity.account = account;
                serviceEntity.type = type;
                service = data.insert(serviceEntity);
            }
            return service;
        }
    }

    @Entity
    @Table(name = "EntryError")
    public static abstract class EntryError {
        @Key
        @Generated
        int id;

        @Column(nullable = false)
        String error;

        @ForeignKey(update = ReferentialAction.CASCADE)
        @OneToOne
        Entry entry;
    }

    static class CollectionInfoConverter implements Converter<CollectionInfo, String> {
        @Override
        public Class<CollectionInfo> getMappedType() {
            return CollectionInfo.class;
        }

        @Override
        public Class<String> getPersistedType() {
            return String.class;
        }

        @Override
        public Integer getPersistedSize() {
            return null;
        }

        @Override
        public String convertToPersisted(CollectionInfo value) {
            return value == null ? null : value.toJson();
        }

        @Override
        public CollectionInfo convertToMapped(Class<? extends CollectionInfo> type, String value) {
            return value == null ? null : CollectionInfo.Companion.fromJson(value);
        }
    }


    static class SyncEntryConverter implements Converter<SyncEntry, String> {
        @Override
        public Class<SyncEntry> getMappedType() {
            return SyncEntry.class;
        }

        @Override
        public Class<String> getPersistedType() {
            return String.class;
        }

        @Override
        public Integer getPersistedSize() {
            return null;
        }

        @Override
        public String convertToPersisted(SyncEntry value) {
            return value == null ? null : value.toJson();
        }

        @Override
        public SyncEntry convertToMapped(Class<? extends SyncEntry> type, String value) {
            return value == null ? null : SyncEntry.Companion.fromJson(value);
        }
    }
}
