import {
  INodeExecutionData,
  INodeType,
  INodeTypeDescription,
  NodeConnectionType,
  IExecuteFunctions
} from 'n8n-workflow';
import { Client } from 'pg';

// Замена $VAR на process.env.VAR
function resolveEnvVariables(str: string): string {
  return str.replace(/\$([a-zA-Z0-9_]+)/g, (_, v) => process.env[v] ?? '');
}

export class _ORMwrite implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'ORM Write',
    name: 'ormWrite',
    group: ['transform'],
    version: 1,
    description: 'Writes data into PostgreSQL table (creates if not exists)',
    defaults: {
      name: 'ORM Write',
    },
    inputs: [NodeConnectionType.Main],
    outputs: [NodeConnectionType.Main],
    properties: [
      { displayName: 'Host', name: 'host', type: 'string', default: '$dbhost', required: true },
      { displayName: 'Port', name: 'port', type: 'string', default: '$dbport' },
      { displayName: 'Database', name: 'database', type: 'string', default: '$dbname', required: true },
      { displayName: 'User', name: 'user', type: 'string', default: '$dbuser' },
      { displayName: 'Password', name: 'password', type: 'string', default: '$dbpassword' },
      { displayName: 'SSL', name: 'ssl', type: 'boolean', default: false },
      { displayName: 'Table Name', name: 'table', type: 'string', default: '', required: true },
      { displayName: 'Create Table If Not Exists', name: 'createIfNotExists', type: 'boolean', default: true },
    ],
  };

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const item = this.getInputData()[0]; // всегда один элемент

    // Формируем METADATA заново
    const nodeInfo = this.getNode();
    const workflowName = this.getWorkflow().name;
    item.json.METADATA = {
      from: nodeInfo.name,
      id: nodeInfo.name,
      workflow: workflowName,
      time: Date.now(),
    };

    // Параметры подключения
    const host = resolveEnvVariables(this.getNodeParameter('host', 0) as string);
    const port = parseInt(resolveEnvVariables(this.getNodeParameter('port', 0) as string), 10) || 5432;
    const database = resolveEnvVariables(this.getNodeParameter('database', 0) as string);
    const user = resolveEnvVariables(this.getNodeParameter('user', 0) as string);
    const password = resolveEnvVariables(this.getNodeParameter('password', 0) as string);
    const ssl = this.getNodeParameter('ssl', 0) as boolean;
    const table = this.getNodeParameter('table', 0) as string;
    const createIfNotExists = this.getNodeParameter('createIfNotExists', 0) as boolean;

    const client = new Client({ host, port, database, user, password, ssl });
    await client.connect();

    // Проверяем существование таблицы
    const tableExistsRes = await client.query(
      `SELECT EXISTS (
        SELECT FROM information_schema.tables
        WHERE table_schema = 'public' AND table_name = $1
      ) AS exists`,
      [table],
    );
    const tableExists = tableExistsRes.rows[0].exists;

    const data = item.json.DATA as { [key: string]: any };
    if (typeof data !== 'object' || data === null) {
      await client.end();
      throw new Error('DATA должно быть объектом в первом элементе');
    }

    // Создаём таблицу, если нужно
    if (!tableExists && createIfNotExists) {
      const columns = Object.entries(data)
        .map(([key, value]) => {
          if (key === 'id') throw new Error("Column 'id' is reserved");
          if (typeof value === 'number' && Number.isInteger(value)) return `"${key}" BIGINT`;
          if (typeof value === 'number') return `"${key}" REAL`;
          if (typeof value === 'boolean') return `"${key}" BOOLEAN`;
          if (typeof value === 'string') return `"${key}" TEXT`;
          return `"${key}" JSONB`;
        })
        .join(', ');
      await client.query(`CREATE TABLE "${table}" (id SERIAL PRIMARY KEY, ${columns});`);
    } else if (!tableExists && !createIfNotExists) {
      await client.end();
      throw new Error(`Table ${table} does not exist`);
    }

    // Получаем список существующих столбцов
    const colRes = await client.query(
      `SELECT column_name FROM information_schema.columns
       WHERE table_schema = 'public' AND table_name = $1 AND column_name != 'id'`,
      [table],
    );
    const columns = colRes.rows.map((r) => r.column_name);

    const usedCols = columns.filter((c) => data[c] !== undefined);
    if (usedCols.length === 0) {
      await client.end();
      throw new Error('Нет данных для вставки: ни один столбец не совпадает');
    }

    const values = usedCols.map((c) => data[c]);
    const placeholders = usedCols.map((_, i) => `$${i + 1}`).join(', ');

    await client.query(
      `INSERT INTO "${table}" (${usedCols.map((c) => `"${c}"`).join(', ')}) VALUES (${placeholders})`,
      values,
    );

    await client.end();

    return this.prepareOutputData([item]);
  }
}
