package squirrel;

import java.sql.*;
import java.util.*;

public class LocalStorage {
    private final Connection conn;

    public LocalStorage() {
        this.conn = Database.getConnection();
    }

    // Save: serialize constructor arguments
    public <T> void save(String key, T value) {
        try {
            var ctor = value.getClass().getDeclaredConstructors()[0];
            ctor.setAccessible(true);

            var fields = value.getClass().getDeclaredFields();
            List<String> pairs = new ArrayList<>();

            for (var f : fields) {
                f.setAccessible(true);
                Object v = f.get(value);
                pairs.add(f.getName() + "=" + (v == null ? "null" : v.toString()));
            }

            try (PreparedStatement ps = conn.prepareStatement(
                "INSERT INTO storage(key, data) VALUES(?, ?) " +
                "ON CONFLICT(key) DO UPDATE SET data=excluded.data"
            )) {
                ps.setString(1, key);
                ps.setString(2, String.join(";", pairs));
                ps.executeUpdate();
            }
        } catch (Exception e) {
            throw new RuntimeException("Save failed", e);
        }
    }

    // Load: reconstruct using the constructor of clazz
    @SuppressWarnings("unchecked")
    public <T> T load(String key, Class<T> clazz) {
        try (PreparedStatement ps = conn.prepareStatement(
            "SELECT data FROM storage WHERE key=?"
        )) {
            ps.setString(1, key);
            ResultSet rs = ps.executeQuery();
            if (!rs.next()) return null;

            String data = rs.getString("data");
            Map<String, String> map = new LinkedHashMap<>();
            for (String pair : data.split(";")) {
                if (pair.isBlank()) continue;
                int idx = pair.indexOf('=');
                if (idx == -1) continue;
                map.put(pair.substring(0, idx), pair.substring(idx + 1));
            }

            var ctor = clazz.getDeclaredConstructors()[0];
            ctor.setAccessible(true);
            Class<?>[] paramTypes = ctor.getParameterTypes();
            Object[] converted = new Object[paramTypes.length];

            int i = 0;
            for (Class<?> pt : paramTypes) {
                String raw = map.values().toArray(new String[0])[i];
                if (pt == int.class || pt == Integer.class) {
                    converted[i] = Integer.parseInt(raw);
                } else if (pt == long.class || pt == Long.class) {
                    converted[i] = Long.parseLong(raw);
                } else if (pt == double.class || pt == Double.class) {
                    converted[i] = Double.parseDouble(raw);
                } else if (pt == String.class) {
                    converted[i] = raw;
                } else {
                    throw new IllegalArgumentException("Unsupported param type: " + pt);
                }
                i++;
            }

            return (T) ctor.newInstance(converted);
        } catch (Exception e) {
            throw new RuntimeException("Load failed", e);
        }
    }

    public void flush() {
        try (Statement stmt = conn.createStatement()) {
            stmt.executeUpdate("DELETE FROM storage");
        } catch (Exception e) {
            throw new RuntimeException("Flush failed", e);
        }
    }
}
