package com.dexorder.flink.iceberg;

import org.apache.iceberg.Schema;
import org.apache.iceberg.Table;
import org.apache.iceberg.catalog.Catalog;
import org.apache.iceberg.catalog.Namespace;
import org.apache.iceberg.catalog.SupportsNamespaces;
import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.iceberg.types.Types;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

import static org.apache.iceberg.types.Types.NestedField.optional;
import static org.apache.iceberg.types.Types.NestedField.required;

/**
 * Initializes Iceberg tables directly via the Iceberg Catalog API.
 *
 * Creates tables in Iceberg if they don't already exist. This ensures all tables
 * are properly initialized before Flink starts writing data.
 */
public class SchemaInitializer {
    private static final Logger LOG = LoggerFactory.getLogger(SchemaInitializer.class);

    private final Catalog catalog;
    private final String namespace;

    public SchemaInitializer(Catalog catalog, String namespace) {
        this.catalog = catalog;
        this.namespace = namespace;
    }

    /**
     * Initialize all schemas by creating tables from definitions.
     *
     * @throws IOException if initialization fails
     */
    public void initializeSchemas() throws IOException {
        LOG.info("Initializing Iceberg schemas in namespace: {}", namespace);

        // Ensure namespace exists
        ensureNamespaceExists();

        // Initialize each table
        initializeOhlcTable();

        // Add more table initializations here as needed

        LOG.info("Schema initialization completed successfully");
    }

    /**
     * Ensure the namespace exists in the catalog.
     */
    private void ensureNamespaceExists() {
        Namespace ns = Namespace.of(namespace);
        try {
            if (catalog instanceof SupportsNamespaces) {
                SupportsNamespaces nsCatalog = (SupportsNamespaces) catalog;
                if (!nsCatalog.namespaceExists(ns)) {
                    nsCatalog.createNamespace(ns);
                    LOG.info("Created namespace: {}", namespace);
                } else {
                    LOG.info("Namespace already exists: {}", namespace);
                }
            } else {
                LOG.warn("Catalog does not support namespaces, skipping namespace creation");
            }
        } catch (Exception e) {
            LOG.error("Failed to create namespace: {}", namespace, e);
            throw new RuntimeException("Namespace creation failed", e);
        }
    }

    /**
     * Initialize the OHLC table if it doesn't exist.
     */
    // Bump this when the schema changes. Tables with a different (or missing) version
    // will be dropped and recreated. Increment by 1 for each incompatible change.
    private static final String OHLC_SCHEMA_VERSION = "1";
    private static final String SCHEMA_VERSION_PROP = "app.schema.version";

    private void initializeOhlcTable() {
        TableIdentifier tableId = TableIdentifier.of(namespace, "ohlc");

        try {
            if (catalog.tableExists(tableId)) {
                Table existing = catalog.loadTable(tableId);
                String existingVersion = existing.properties().get(SCHEMA_VERSION_PROP);
                if (!OHLC_SCHEMA_VERSION.equals(existingVersion)) {
                    LOG.warn("Table {} has schema version '{}', expected '{}' — manual migration required",
                            tableId, existingVersion, OHLC_SCHEMA_VERSION);
                }
                LOG.info("Table {} already exists at schema version {} — skipping creation", tableId, existingVersion);
                return;
            }

            LOG.info("Creating OHLC table: {}", tableId);

            // Define the OHLC schema.
            // timestamp is stored as BIGINT (microseconds since epoch), not a TIMESTAMP type,
            // so that GenericRowData.setField() accepts a plain Long value.
            Schema schema = new Schema(
                // Primary key fields
                required(1, "ticker", Types.StringType.get(), "Market identifier (e.g., BINANCE:BTC/USDT)"),
                required(2, "period_seconds", Types.IntegerType.get(), "OHLC period in seconds"),
                required(3, "timestamp", Types.LongType.get(), "Candle timestamp in microseconds since epoch"),

                // OHLC price data
                required(4, "open", Types.LongType.get(), "Opening price"),
                required(5, "high", Types.LongType.get(), "Highest price"),
                required(6, "low", Types.LongType.get(), "Lowest price"),
                required(7, "close", Types.LongType.get(), "Closing price"),

                // Volume data
                optional(8, "volume", Types.LongType.get(), "Total volume"),
                optional(9, "buy_vol", Types.LongType.get(), "Buy volume"),
                optional(10, "sell_vol", Types.LongType.get(), "Sell volume"),

                // Timing data
                optional(11, "open_time", Types.LongType.get(), "Timestamp when open price occurred"),
                optional(12, "high_time", Types.LongType.get(), "Timestamp when high price occurred"),
                optional(13, "low_time", Types.LongType.get(), "Timestamp when low price occurred"),
                optional(14, "close_time", Types.LongType.get(), "Timestamp when close price occurred"),

                // Additional fields
                optional(15, "open_interest", Types.LongType.get(), "Open interest for futures"),

                // Metadata fields
                optional(16, "request_id", Types.StringType.get(), "Request ID that generated this data"),
                required(17, "ingested_at", Types.LongType.get(), "Timestamp when data was ingested by Flink")
            );

            // Create the table with partitioning and properties
            Table table = catalog.buildTable(tableId, schema)
                    .withPartitionSpec(org.apache.iceberg.PartitionSpec.builderFor(schema)
                            .identity("ticker")
                            .build())
                    .withProperty("write.format.default", "parquet")
                    .withProperty("write.parquet.compression-codec", "snappy")
                    .withProperty("write.metadata.compression-codec", "gzip")
                    .withProperty("format-version", "2")
                    .withProperty(SCHEMA_VERSION_PROP, OHLC_SCHEMA_VERSION)
                    .create();

            LOG.info("Successfully created OHLC table: {}", tableId);

        } catch (Exception e) {
            LOG.error("Failed to initialize OHLC table: {}", tableId, e);
            throw new RuntimeException("OHLC table initialization failed", e);
        }
    }
}
