const Database = require("better-sqlite3");
const path = require("path");

const dbPath = path.join(__dirname, "..", "data", "polygons.db");
const db = new Database(dbPath);
db.pragma("foreign_keys = ON");

db.prepare(`
    CREATE TABLE IF NOT EXISTS geometries (
        id TEXT PRIMARY KEY,
        type TEXT,
        is_deleted INTEGER NOT NULL DEFAULT 0,
        draw_geometry TEXT NOT NULL,
        binding TEXT,
        time_start INTEGER,
        time_end INTEGER,
        bbox_min_lng REAL,
        bbox_min_lat REAL,
        bbox_max_lng REAL,
        bbox_max_lat REAL,
        created_at TEXT,
        updated_at TEXT
    )
`).run();

db.prepare(`
    CREATE TABLE IF NOT EXISTS entities (
        id TEXT PRIMARY KEY,
        name TEXT NOT NULL,
        slug TEXT UNIQUE,
        description TEXT,
        type_id TEXT NOT NULL DEFAULT 'country',
        status INTEGER DEFAULT 1,
        is_deleted INTEGER NOT NULL DEFAULT 0,
        created_at TEXT,
        updated_at TEXT
    )
`).run();

db.prepare(`
    CREATE TABLE IF NOT EXISTS entity_geometries (
        entity_id TEXT NOT NULL,
        geometry_id TEXT NOT NULL,
        created_at TEXT,
        PRIMARY KEY (entity_id, geometry_id),
        FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE,
        FOREIGN KEY (geometry_id) REFERENCES geometries(id) ON DELETE CASCADE
    )
`).run();

db.prepare(`
    CREATE TABLE IF NOT EXISTS sections (
        id TEXT PRIMARY KEY,
        title TEXT NOT NULL,
        description TEXT,
        user_id TEXT,
        created_by TEXT,
        created_at TEXT NOT NULL,
        updated_at TEXT NOT NULL
    )
`).run();

db.prepare(`
    CREATE TABLE IF NOT EXISTS section_states (
        section_id TEXT PRIMARY KEY,
        status TEXT NOT NULL DEFAULT 'editing',
        head_commit_id TEXT,
        version INTEGER NOT NULL DEFAULT 0,
        locked_by TEXT,
        locked_at TEXT,
        lock_expires_at TEXT,
        updated_at TEXT NOT NULL,
        FOREIGN KEY (section_id) REFERENCES sections(id) ON DELETE CASCADE
    )
`).run();

db.prepare(`
    CREATE TABLE IF NOT EXISTS section_commits (
        id TEXT PRIMARY KEY,
        section_id TEXT NOT NULL,
        parent_commit_id TEXT,
        commit_no INTEGER NOT NULL,
        kind TEXT NOT NULL DEFAULT 'manual',
        restored_from_commit_id TEXT,
        created_by TEXT NOT NULL,
        created_at TEXT NOT NULL,
        title TEXT,
        note TEXT,
        snapshot_json TEXT NOT NULL,
        snapshot_hash TEXT,
        FOREIGN KEY (section_id) REFERENCES sections(id) ON DELETE CASCADE,
        FOREIGN KEY (parent_commit_id) REFERENCES section_commits(id),
        FOREIGN KEY (restored_from_commit_id) REFERENCES section_commits(id)
    )
`).run();

db.prepare(`
    CREATE TABLE IF NOT EXISTS section_submissions (
        id TEXT PRIMARY KEY,
        section_id TEXT NOT NULL,
        commit_id TEXT NOT NULL,
        submitted_by TEXT NOT NULL,
        submitted_at TEXT NOT NULL,
        status TEXT NOT NULL DEFAULT 'pending',
        reviewed_by TEXT,
        reviewed_at TEXT,
        review_note TEXT,
        snapshot_json TEXT NOT NULL,
        snapshot_hash TEXT,
        FOREIGN KEY (section_id) REFERENCES sections(id) ON DELETE CASCADE,
        FOREIGN KEY (commit_id) REFERENCES section_commits(id)
    )
`).run();

ensureColumn("entities", "status", "INTEGER DEFAULT 1");
ensureColumn("entities", "is_deleted", "INTEGER NOT NULL DEFAULT 0");
ensureColumn("entities", "type_id", "TEXT NOT NULL DEFAULT 'country'");
ensureColumn("geometries", "is_deleted", "INTEGER NOT NULL DEFAULT 0");
ensureColumn("geometries", "binding", "TEXT");
ensureColumn("sections", "user_id", "TEXT");
dropEntityDeprecatedColumnsIfExists();
dropGeometryDeprecatedColumnsIfExists();
migrateLegacyGeometryTypeTokens();

db.prepare(`
    CREATE UNIQUE INDEX IF NOT EXISTS idx_entities_slug
    ON entities(slug)
`).run();

db.prepare(`
    CREATE INDEX IF NOT EXISTS idx_entities_name
    ON entities(name)
`).run();

db.prepare(`DROP INDEX IF EXISTS idx_entity_geometries_geometry_id`).run();

db.prepare(`
    CREATE INDEX IF NOT EXISTS idx_entity_geometries_geometry_id
    ON entity_geometries(geometry_id)
`).run();

db.prepare(`
    CREATE INDEX IF NOT EXISTS idx_entity_geometries_entity_id
    ON entity_geometries(entity_id)
`).run();

db.prepare(`
    CREATE INDEX IF NOT EXISTS idx_section_states_status
    ON section_states(status, updated_at)
`).run();

db.prepare(`
    CREATE UNIQUE INDEX IF NOT EXISTS idx_section_commits_no
    ON section_commits(section_id, commit_no)
`).run();

db.prepare(`
    CREATE INDEX IF NOT EXISTS idx_section_commits_section_time
    ON section_commits(section_id, created_at)
`).run();

db.prepare(`
    CREATE INDEX IF NOT EXISTS idx_section_submissions_section_status
    ON section_submissions(section_id, status, submitted_at)
`).run();

module.exports = db;

function ensureColumn(tableName, columnName, columnDefinition) {
    const columns = db.prepare(`PRAGMA table_info(${tableName})`).all();
    const hasColumn = columns.some((column) => column.name === columnName);
    if (hasColumn) return;
    db.prepare(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnDefinition}`).run();
}

function dropGeometryDeprecatedColumnsIfExists() {
    db.prepare(`DROP INDEX IF EXISTS idx_geometries_kind`).run();

    const columns = db.prepare(`PRAGMA table_info(geometries)`).all();
    const hasKind = columns.some((column) => column.name === "kind");
    const hasLineMode = columns.some((column) => column.name === "line_mode");
    if (!hasKind && !hasLineMode) return;
    rebuildGeometriesTableToCurrentSchema(columns);
}

function dropEntityDeprecatedColumnsIfExists() {
    const columns = db.prepare(`PRAGMA table_info(entities)`).all();
    const hasKind = columns.some((column) => column.name === "kind");
    if (!hasKind) return;
    rebuildEntitiesTableToCurrentSchema(columns);
}

function rebuildEntitiesTableToCurrentSchema(existingColumns = null) {
    const foreignKeysEnabled = Number(db.pragma("foreign_keys", { simple: true })) === 1;
    const columns = existingColumns || db.prepare(`PRAGMA table_info(entities)`).all();
    const hasSlug = columns.some((column) => column.name === "slug");
    const hasDescription = columns.some((column) => column.name === "description");
    const hasTypeId = columns.some((column) => column.name === "type_id");
    const hasStatus = columns.some((column) => column.name === "status");
    const hasIsDeleted = columns.some((column) => column.name === "is_deleted");
    const hasCreatedAt = columns.some((column) => column.name === "created_at");
    const hasUpdatedAt = columns.some((column) => column.name === "updated_at");

    const slugSelect = hasSlug ? "slug" : "NULL";
    const descriptionSelect = hasDescription ? "description" : "NULL";
    const typeIdSelect = hasTypeId ? "type_id" : "'country'";
    const statusSelect = hasStatus ? "status" : "1";
    const isDeletedSelect = hasIsDeleted ? "is_deleted" : "0";
    const createdAtSelect = hasCreatedAt ? "created_at" : "NULL";
    const updatedAtSelect = hasUpdatedAt ? "updated_at" : "NULL";

    if (foreignKeysEnabled) {
        db.pragma("foreign_keys = OFF");
    }

    try {
        const tx = db.transaction(() => {
            db.prepare(`DROP TABLE IF EXISTS entities_new`).run();

            db.prepare(`
                CREATE TABLE entities_new (
                    id TEXT PRIMARY KEY,
                    name TEXT NOT NULL,
                    slug TEXT UNIQUE,
                    description TEXT,
                    type_id TEXT NOT NULL DEFAULT 'country',
                    status INTEGER DEFAULT 1,
                    is_deleted INTEGER NOT NULL DEFAULT 0,
                    created_at TEXT,
                    updated_at TEXT
                )
            `).run();

            db.prepare(`
                INSERT INTO entities_new (
                    id,
                    name,
                    slug,
                    description,
                    type_id,
                    status,
                    is_deleted,
                    created_at,
                    updated_at
                )
                SELECT
                    id,
                    name,
                    ${slugSelect},
                    ${descriptionSelect},
                    ${typeIdSelect},
                    ${statusSelect},
                    ${isDeletedSelect},
                    ${createdAtSelect},
                    ${updatedAtSelect}
                FROM entities
            `).run();

            db.prepare(`DROP TABLE entities`).run();
            db.prepare(`ALTER TABLE entities_new RENAME TO entities`).run();
        });

        tx();
    } finally {
        if (foreignKeysEnabled) {
            db.pragma("foreign_keys = ON");
        }
    }
}

function rebuildGeometriesTableToCurrentSchema(existingColumns = null) {
    const foreignKeysEnabled = Number(db.pragma("foreign_keys", { simple: true })) === 1;
    const columns = existingColumns || db.prepare(`PRAGMA table_info(geometries)`).all();
    const hasType = columns.some((column) => column.name === "type");
    const hasIsDeleted = columns.some((column) => column.name === "is_deleted");
    const hasBinding = columns.some((column) => column.name === "binding");
    const hasTimeStart = columns.some((column) => column.name === "time_start");
    const hasTimeEnd = columns.some((column) => column.name === "time_end");
    const hasBBoxMinLng = columns.some((column) => column.name === "bbox_min_lng");
    const hasBBoxMinLat = columns.some((column) => column.name === "bbox_min_lat");
    const hasBBoxMaxLng = columns.some((column) => column.name === "bbox_max_lng");
    const hasBBoxMaxLat = columns.some((column) => column.name === "bbox_max_lat");
    const hasCreatedAt = columns.some((column) => column.name === "created_at");
    const hasUpdatedAt = columns.some((column) => column.name === "updated_at");

    const typeSelect = hasType ? "type" : "NULL";
    const isDeletedSelect = hasIsDeleted ? "is_deleted" : "0";
    const bindingSelect = hasBinding ? "binding" : "NULL";
    const timeStartSelect = hasTimeStart ? "time_start" : "NULL";
    const timeEndSelect = hasTimeEnd ? "time_end" : "NULL";
    const bboxMinLngSelect = hasBBoxMinLng ? "bbox_min_lng" : "NULL";
    const bboxMinLatSelect = hasBBoxMinLat ? "bbox_min_lat" : "NULL";
    const bboxMaxLngSelect = hasBBoxMaxLng ? "bbox_max_lng" : "NULL";
    const bboxMaxLatSelect = hasBBoxMaxLat ? "bbox_max_lat" : "NULL";
    const createdAtSelect = hasCreatedAt ? "created_at" : "NULL";
    const updatedAtSelect = hasUpdatedAt ? "updated_at" : "NULL";
    if (foreignKeysEnabled) {
        db.pragma("foreign_keys = OFF");
    }

    try {
        const tx = db.transaction(() => {
            db.prepare(`DROP TABLE IF EXISTS geometries_new`).run();

            db.prepare(`
                CREATE TABLE geometries_new (
                    id TEXT PRIMARY KEY,
                    type TEXT,
                    is_deleted INTEGER NOT NULL DEFAULT 0,
                    draw_geometry TEXT NOT NULL,
                    binding TEXT,
                    time_start INTEGER,
                    time_end INTEGER,
                    bbox_min_lng REAL,
                    bbox_min_lat REAL,
                    bbox_max_lng REAL,
                    bbox_max_lat REAL,
                    created_at TEXT,
                    updated_at TEXT
                )
            `).run();

            db.prepare(`
                INSERT INTO geometries_new (
                    id,
                    type,
                    is_deleted,
                    draw_geometry,
                    binding,
                    time_start,
                    time_end,
                    bbox_min_lng,
                    bbox_min_lat,
                    bbox_max_lng,
                    bbox_max_lat,
                    created_at,
                    updated_at
                )
                SELECT
                    id,
                    ${typeSelect},
                    ${isDeletedSelect},
                    draw_geometry,
                    ${bindingSelect},
                    ${timeStartSelect},
                    ${timeEndSelect},
                    ${bboxMinLngSelect},
                    ${bboxMinLatSelect},
                    ${bboxMaxLngSelect},
                    ${bboxMaxLatSelect},
                    ${createdAtSelect},
                    ${updatedAtSelect}
                FROM geometries
            `).run();

            db.prepare(`DROP TABLE geometries`).run();
            db.prepare(`ALTER TABLE geometries_new RENAME TO geometries`).run();
        });

        tx();
    } finally {
        if (foreignKeysEnabled) {
            db.pragma("foreign_keys = ON");
        }
    }
}

function migrateLegacyGeometryTypeTokens() {
    const geometryColumns = db.prepare(`PRAGMA table_info(geometries)`).all();
    const hasType = geometryColumns.some((column) => column.name === "type");
    if (!hasType) return;

    const tx = db.transaction(() => {
        const legacyRows = db.prepare(`
            SELECT
                g.id,
                (
                    SELECT e.type_id
                    FROM entity_geometries eg
                    JOIN entities e
                      ON e.id = eg.entity_id
                     AND e.is_deleted = 0
                    WHERE eg.geometry_id = g.id
                    ORDER BY eg.rowid ASC
                    LIMIT 1
                ) AS primary_entity_type
            FROM geometries g
            WHERE g.type IS NULL
               OR TRIM(g.type) = ''
               OR LOWER(TRIM(g.type)) IN ('line', 'path')
        `).all();

        const updateTypeStmt = db.prepare(`
            UPDATE geometries
            SET type = ?
            WHERE id = ?
        `);

        for (const row of legacyRows) {
            const semanticType = normalizeText(row.primary_entity_type);
            updateTypeStmt.run(semanticType, row.id);
        }
    });

    tx();
}

function normalizeText(value) {
    if (value === undefined || value === null) return null;
    const normalized = String(value).trim().toLowerCase();
    return normalized.length ? normalized : null;
}
