<?php

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class Niwa_AI_Admin_Autonomy_Store {
    const TABLE_SLUG = 'niwa_ai_admin_runs';

    public function maybe_ensure_table_schema() {
        global $wpdb;

        $table_name = $this->get_table_name();
        $existing   = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) );

        if ( $table_name !== $existing ) {
            $this->ensure_table_schema();
        }
    }

    public function ensure_table_schema() {
        global $wpdb;

        $table_name      = $this->get_table_name();
        $charset_collate = $wpdb->get_charset_collate();

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';

        $sql = "CREATE TABLE {$table_name} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            run_uuid varchar(64) NOT NULL,
            source varchar(32) NOT NULL DEFAULT 'telegram',
            prompt_text longtext NOT NULL,
            plan_json longtext NULL,
            approval_state varchar(20) NOT NULL DEFAULT 'approved',
            execution_state varchar(20) NOT NULL DEFAULT 'planned',
            rollback_status varchar(20) NOT NULL DEFAULT 'none',
            context_json longtext NULL,
            executed_actions_json longtext NULL,
            rollback_json longtext NULL,
            validation_error longtext NULL,
            last_error longtext NULL,
            approved_at datetime NULL,
            executed_at datetime NULL,
            rolled_back_at datetime NULL,
            created_at datetime NOT NULL,
            updated_at datetime NOT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY run_uuid (run_uuid),
            KEY approval_state (approval_state),
            KEY execution_state (execution_state),
            KEY rollback_status (rollback_status),
            KEY created_at (created_at)
        ) {$charset_collate};";

        dbDelta( $sql );
    }

    public function get_table_name() {
        global $wpdb;
        return $wpdb->prefix . self::TABLE_SLUG;
    }

    public function create_run( $args ) {
        global $wpdb;

        $now   = current_time( 'mysql', true );
        $table = $this->get_table_name();
        $uuid  = function_exists( 'wp_generate_uuid4' ) ? wp_generate_uuid4() : uniqid( 'niwa_', true );

        $data = array(
            'run_uuid'              => sanitize_text_field( $uuid ),
            'source'                => isset( $args['source'] ) ? sanitize_key( $args['source'] ) : 'telegram',
            'prompt_text'           => isset( $args['prompt_text'] ) ? (string) $args['prompt_text'] : '',
            'plan_json'             => $this->encode_json( isset( $args['plan'] ) ? $args['plan'] : array() ),
            'approval_state'        => isset( $args['approval_state'] ) ? sanitize_key( $args['approval_state'] ) : 'approved',
            'execution_state'       => isset( $args['execution_state'] ) ? sanitize_key( $args['execution_state'] ) : 'planned',
            'rollback_status'       => isset( $args['rollback_status'] ) ? sanitize_key( $args['rollback_status'] ) : 'none',
            'context_json'          => $this->encode_json( isset( $args['context'] ) ? $args['context'] : array() ),
            'executed_actions_json' => $this->encode_json( isset( $args['executed_actions'] ) ? $args['executed_actions'] : array() ),
            'rollback_json'         => $this->encode_json( isset( $args['rollback_entries'] ) ? $args['rollback_entries'] : array() ),
            'validation_error'      => isset( $args['validation_error'] ) ? (string) $args['validation_error'] : '',
            'last_error'            => isset( $args['last_error'] ) ? (string) $args['last_error'] : '',
            'created_at'            => $now,
            'updated_at'            => $now,
        );

        $inserted = $wpdb->insert(
            $table,
            $data,
            array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' )
        );

        if ( false === $inserted ) {
            return null;
        }

        return $this->get_run( (int) $wpdb->insert_id );
    }

    public function get_run( $run_id ) {
        global $wpdb;

        $row = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT * FROM {$this->get_table_name()} WHERE id = %d LIMIT 1",
                intval( $run_id )
            ),
            ARRAY_A
        );

        return $this->hydrate_row( $row );
    }

    public function list_recent_runs( $limit = 10, $executed_after_utc = '', $executed_before_utc = '' ) {
        global $wpdb;

        $limit = max( 1, intval( $limit ) );
        if ( '' !== trim( (string) $executed_after_utc ) && '' !== trim( (string) $executed_before_utc ) ) {
            $query = $wpdb->prepare(
                "SELECT * FROM {$this->get_table_name()} WHERE executed_at >= %s AND executed_at < %s ORDER BY executed_at DESC, id DESC LIMIT %d",
                (string) $executed_after_utc,
                (string) $executed_before_utc,
                $limit
            );
        } else {
            $query = $wpdb->prepare(
                "SELECT * FROM {$this->get_table_name()} ORDER BY id DESC LIMIT %d",
                $limit
            );
        }

        $rows = $wpdb->get_results( $query, ARRAY_A );

        $runs = array();
        foreach ( (array) $rows as $row ) {
            $runs[] = $this->hydrate_row( $row );
        }

        return $runs;
    }

    public function get_latest_completed_run_for_rollback() {
        global $wpdb;

        $row = $wpdb->get_row(
            "SELECT * FROM {$this->get_table_name()} WHERE execution_state = 'succeeded' AND rollback_status IN ('available', 'failed') ORDER BY executed_at DESC, id DESC LIMIT 1",
            ARRAY_A
        );

        return $this->hydrate_row( $row );
    }

    public function mark_execution_started( $run_id ) {
        $now = current_time( 'mysql', true );

        return $this->update_run(
            $run_id,
            array(
                'approval_state'  => 'approved',
                'execution_state' => 'running',
                'approved_at'     => $now,
            )
        );
    }

    public function mark_execution_result( $run_id, $status, $args = array() ) {
        $now = current_time( 'mysql', true );

        $data = array(
            'execution_state'       => sanitize_key( $status ),
            'executed_actions_json' => $this->encode_json( isset( $args['executed_actions'] ) ? $args['executed_actions'] : array() ),
            'rollback_json'         => $this->encode_json( isset( $args['rollback_entries'] ) ? $args['rollback_entries'] : array() ),
            'validation_error'      => isset( $args['validation_error'] ) ? (string) $args['validation_error'] : '',
            'last_error'            => isset( $args['last_error'] ) ? (string) $args['last_error'] : '',
            'rollback_status'       => isset( $args['rollback_status'] ) ? sanitize_key( $args['rollback_status'] ) : 'none',
            'executed_at'           => $now,
        );

        if ( isset( $args['approval_state'] ) ) {
            $data['approval_state'] = sanitize_key( $args['approval_state'] );
        }

        return $this->update_run( $run_id, $data );
    }

    public function mark_rolled_back( $run_id, $status, $error = '' ) {
        $now = current_time( 'mysql', true );

        return $this->update_run(
            $run_id,
            array(
                'rollback_status' => sanitize_key( $status ),
                'rolled_back_at'  => $now,
                'last_error'      => (string) $error,
            )
        );
    }

    private function update_run( $run_id, $data ) {
        global $wpdb;

        $table   = $this->get_table_name();
        $run_id  = intval( $run_id );
        $formats = array();

        $allowed_fields = array(
            'approval_state'        => '%s',
            'execution_state'       => '%s',
            'rollback_status'       => '%s',
            'plan_json'             => '%s',
            'context_json'          => '%s',
            'executed_actions_json' => '%s',
            'rollback_json'         => '%s',
            'validation_error'      => '%s',
            'last_error'            => '%s',
            'approved_at'           => '%s',
            'executed_at'           => '%s',
            'rolled_back_at'        => '%s',
        );

        $payload = array();
        foreach ( $allowed_fields as $field => $format ) {
            if ( ! array_key_exists( $field, $data ) ) {
                continue;
            }
            $payload[ $field ] = $data[ $field ];
            $formats[]         = $format;
        }

        if ( empty( $payload ) ) {
            return $this->get_run( $run_id );
        }

        $payload['updated_at'] = current_time( 'mysql', true );
        $formats[]             = '%s';

        $updated = $wpdb->update(
            $table,
            $payload,
            array( 'id' => $run_id ),
            $formats,
            array( '%d' )
        );

        if ( false === $updated ) {
            return null;
        }

        return $this->get_run( $run_id );
    }

    private function hydrate_row( $row ) {
        if ( empty( $row ) || ! is_array( $row ) ) {
            return null;
        }

        $row['id']               = isset( $row['id'] ) ? intval( $row['id'] ) : 0;
        $row['plan']             = $this->decode_json( isset( $row['plan_json'] ) ? $row['plan_json'] : '' );
        $row['context']          = $this->decode_json( isset( $row['context_json'] ) ? $row['context_json'] : '' );
        $row['executed_actions'] = $this->decode_json( isset( $row['executed_actions_json'] ) ? $row['executed_actions_json'] : '' );
        $row['rollback_entries'] = $this->decode_json( isset( $row['rollback_json'] ) ? $row['rollback_json'] : '' );

        return $row;
    }

    private function encode_json( $value ) {
        if ( empty( $value ) ) {
            return '';
        }

        return wp_json_encode( $value );
    }

    private function decode_json( $value ) {
        if ( '' === (string) $value ) {
            return array();
        }

        $decoded = json_decode( (string) $value, true );
        return is_array( $decoded ) ? $decoded : array();
    }
}
