/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.sql;

import de.steamwar.sql.internal.*;
import lombok.AllArgsConstructor;
import lombok.Getter;

import javax.swing.plaf.nimbus.State;
import java.io.*;
import java.sql.PreparedStatement;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.zip.GZIPInputStream;

@AllArgsConstructor
@Getter
public class NodeData {
    static {
        new SqlTypeMapper<>(PipedInputStream.class, "BLOB", (rs, identifier) -> { throw new SecurityException("PipedInputStream is write only datatype"); }, PreparedStatement::setBinaryStream);
        new SqlTypeMapper<>(ByteArrayInputStream.class, "BLOB", (rs, identifier) -> { throw new SecurityException("ByteArrayInputStream is write only datatype"); }, PreparedStatement::setBinaryStream);
        new SqlTypeMapper<>(BufferedInputStream.class, "BLOB", (rs, identifier) -> { throw new SecurityException("BufferedInputStream is write only datatype"); }, PreparedStatement::setBinaryStream);

        SqlTypeMapper.ordinalEnumMapper(SchematicFormat.class);
    }

    private static final Table<NodeData> table = new Table<>(NodeData.class);

    private static final Statement updateDatabase = new Statement("INSERT INTO NodeData(NodeId, NodeFormat, SchemData) VALUES (?, ?, ?)", true);
    private static final Statement selSchemData = new Statement("SELECT SchemData FROM NodeData WHERE NodeId = ? AND CreatedAt = ?");
    private static final Statement delete = table.delete(Table.PRIMARY);

    private static final SelectStatement<NodeData> get = new SelectStatement<>(table, "SELECT NodeId, CreatedAt, NodeFormat FROM NodeData WHERE NodeId = ? ORDER BY CreatedAt ");
    private static final Statement getRevisions = new Statement("SELECT COUNT(DISTINCT CreatedAt) as CNT FROM NodeData WHERE NodeId = ?");
    private static final SelectStatement<NodeData> getLatest = new SelectStatement<>(table, "SELECT NodeId, CreatedAt, NodeFormat FROM NodeData WHERE NodeId = ? ORDER BY CreatedAt DESC LIMIT 1");

    public static NodeData getLatest(SchematicNode node) {
        if (node.isDir()) throw new IllegalArgumentException("Node is dir");
        return Optional.ofNullable(getLatest.select(node)).orElseGet(() -> new NodeData(node.getId(), Timestamp.from(Instant.now()), SchematicFormat.MCEDIT));
    }

    public static List<NodeData> get(SchematicNode node) {
        return get.listSelect(node);
    }

    public static NodeData get(SchematicNode node, int revision) {
        return get.listSelect(node).get(revision - 1);
    }

    public static int getRevisions(SchematicNode node) {
        return getRevisions.select(rs -> {
            if (rs.next()) {
                return rs.getInt("CNT");
            } else {
                return 0;
            }
        }, node);
    }

    public static void saveFromStream(SchematicNode node, InputStream blob, SchematicFormat format) {
        updateDatabase.update(node.getId(), format, blob);
    }

    @Field(keys = {Table.PRIMARY})
    private final int nodeId;

    @Field(keys = {Table.PRIMARY})
    private Timestamp createdAt;

    @Field
    private SchematicFormat nodeFormat;

    public InputStream schemData() throws IOException {
        return schemData(true);
    }

    public InputStream schemData(boolean decompress) throws IOException {
        try {
            return selSchemData.select(rs -> {
                rs.next();
                InputStream schemData = rs.getBinaryStream("SchemData");
                try {
                    if(rs.wasNull() || schemData.available() == 0) {
                        throw new SecurityException("SchemData is null");
                    }
                    if (decompress) {
                        return new GZIPInputStream(schemData);
                    } else {
                        return schemData;
                    }
                } catch (IOException e) {
                    throw new SecurityException("SchemData is wrong", e);
                }
            }, nodeId, createdAt);
        } catch (Exception e) {
            throw new IOException(e);
        }
    }

    @Deprecated
    public void saveFromStream(InputStream blob, SchematicFormat newFormat) {
        saveFromStream(SchematicNode.getSchematicNode(nodeId), blob, newFormat);
    }

    public void delete() {
        delete.update(nodeId, createdAt);
    }

    @AllArgsConstructor
    @Getter
    public enum SchematicFormat {
        MCEDIT(".schematic"),
        SPONGE_V2(".schem"),
        SPONGE_V3(".schem");

        private final String fileEnding;
    }
}
