package com.yimeng.okr.config;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.function.Consumer;

@Slf4j
@Component
@RequiredArgsConstructor
@Order(2)
public class DatabaseMigrationRunner implements CommandLineRunner {

    private final JdbcTemplate jdbcTemplate;

    @Override
    public void run(String... args) {
        jdbcTemplate.execute("""
                CREATE TABLE IF NOT EXISTS schema_migration (
                    version INTEGER PRIMARY KEY,
                    description TEXT NOT NULL,
                    applied_at TEXT NOT NULL
                )
                """);

        MigrationContext context = new MigrationContext(jdbcTemplate);
        for (DatabaseMigration migration : migrations()) {
            if (isApplied(migration.version())) {
                continue;
            }
            log.info("Applying database migration {} - {}", migration.version(), migration.description());
            migration.apply(context);
            jdbcTemplate.update("""
                    INSERT INTO schema_migration(version, description, applied_at)
                    VALUES (?, ?, datetime('now'))
                    """, migration.version(), migration.description());
            log.info("Database migration {} applied", migration.version());
        }
    }

    private boolean isApplied(int version) {
        Integer count = jdbcTemplate.queryForObject(
                "SELECT COUNT(*) FROM schema_migration WHERE version = ?",
                Integer.class, version);
        return count != null && count > 0;
    }

    private List<DatabaseMigration> migrations() {
        return List.of(new DatabaseMigration(1, "baseline compatibility columns and workflow tables", context -> {
            context.addColumnIfMissing("sys_user", "scopes", "TEXT DEFAULT '[]'");
            context.addColumnIfMissing("sys_user", "status", "VARCHAR(20) NOT NULL DEFAULT 'ACTIVE'");
            context.addColumnIfMissing("sys_user", "disabled_at", "TEXT");
            context.addColumnIfMissing("sys_user", "left_at", "TEXT");

            context.addColumnIfMissing("okr_objective", "level", "VARCHAR(16) NOT NULL DEFAULT 'INDIVIDUAL'");
            context.addColumnIfMissing("okr_objective", "parent_objective_id", "INTEGER DEFAULT NULL");
            context.addColumnIfMissing("okr_objective", "sort_order", "INTEGER DEFAULT 0");
            context.addColumnIfMissing("okr_objective", "progress", "REAL DEFAULT 0");

            context.addColumnIfMissing("performance_score", "status", "VARCHAR(32)");
            context.addColumnIfMissing("performance_score", "published_at", "TEXT");
            context.addColumnIfMissing("performance_score", "confirmed_at", "TEXT");

            context.addColumnIfMissing("performance_feedback", "status", "VARCHAR(32)");
            context.addColumnIfMissing("performance_feedback", "closed_at", "TEXT");

            context.execute("""
                    CREATE TABLE IF NOT EXISTS period_participant (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        period_id INTEGER NOT NULL,
                        user_id INTEGER NOT NULL,
                        status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
                        reason TEXT,
                        operated_by INTEGER,
                        exempted_at TEXT,
                        created_at TEXT NOT NULL,
                        updated_at TEXT NOT NULL
                    )
                    """);
            context.execute("CREATE UNIQUE INDEX IF NOT EXISTS uk_period_participant ON period_participant(period_id, user_id)");

            context.execute("""
                    CREATE TABLE IF NOT EXISTS score_appeal (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        period_id INTEGER NOT NULL,
                        user_id INTEGER NOT NULL,
                        score_id INTEGER NOT NULL,
                        status VARCHAR(20) NOT NULL DEFAULT 'OPEN',
                        reason TEXT NOT NULL,
                        reply TEXT,
                        submitted_at TEXT,
                        resolved_by INTEGER,
                        resolved_at TEXT,
                        created_at TEXT NOT NULL,
                        updated_at TEXT NOT NULL
                    )
                    """);

            context.execute("UPDATE performance_score SET status = 'PUBLISHED' WHERE type = 'FINAL' AND status IS NULL");
        }));
    }

    private record DatabaseMigration(int version, String description, Consumer<MigrationContext> operation) {
        void apply(MigrationContext context) {
            operation.accept(context);
        }
    }

    static class MigrationContext {
        private final JdbcTemplate jdbcTemplate;

        MigrationContext(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }

        void execute(String sql) {
            jdbcTemplate.execute(sql);
        }

        void addColumnIfMissing(String tableName, String columnName, String definition) {
            if (!tableExists(tableName) || columnExists(tableName, columnName)) {
                return;
            }
            jdbcTemplate.execute("ALTER TABLE " + tableName + " ADD COLUMN " + columnName + " " + definition);
        }

        boolean tableExists(String tableName) {
            Integer count = jdbcTemplate.queryForObject("""
                    SELECT COUNT(*) FROM sqlite_master
                    WHERE type = 'table' AND name = ?
                    """, Integer.class, tableName);
            return count != null && count > 0;
        }

        boolean columnExists(String tableName, String columnName) {
            return jdbcTemplate.queryForList("PRAGMA table_info(" + tableName + ")").stream()
                    .anyMatch(row -> columnName.equalsIgnoreCase(String.valueOf(row.get("name"))));
        }
    }
}
