import Database from 'better-sqlite3';
import { ToolDef } from '../../llm/openai-compat.js';
import type { ToolContext, ToolResult } from './core.js';
import { resolveAndGuard } from './core.js';
import { logger } from '../../logger.js';

// --- SQL statement analysis ---

// Extract the first keyword from a SQL statement (trimmed, uppercased)
function firstKeyword(sql: string): string {
  return sql.trim().split(/\s+/)[0]?.toUpperCase() ?? '';
}

// DDL keywords that are always forbidden
const ALWAYS_BLOCKED = new Set(['DROP', 'ALTER', 'ATTACH', 'DETACH', 'REINDEX', 'VACUUM']);

// Compound blocked patterns: "CREATE INDEX", "PRAGMA xxx" (except table_info/table_list)
function isStatementBlocked(sql: string): { blocked: boolean; reason?: string } {
  const trimmed = sql.trim();
  if (trimmed.length === 0) return { blocked: false };

  const kw = firstKeyword(trimmed);

  if (ALWAYS_BLOCKED.has(kw)) {
    return { blocked: true, reason: `"${kw}" statements are not allowed` };
  }

  if (kw === 'CREATE') {
    // Allow CREATE TABLE / CREATE VIEW but block CREATE INDEX / CREATE TRIGGER
    const secondKw = trimmed.trim().split(/\s+/)[1]?.toUpperCase() ?? '';
    const thirdKw = trimmed.trim().split(/\s+/)[2]?.toUpperCase() ?? '';
    // CREATE UNIQUE INDEX is also blocked
    if (secondKw === 'INDEX' || (secondKw === 'UNIQUE' && thirdKw === 'INDEX') || secondKw === 'TRIGGER') {
      return { blocked: true, reason: `"CREATE ${secondKw}" is not allowed` };
    }
  }

  if (kw === 'PRAGMA') {
    // Allow only PRAGMA table_info and PRAGMA table_list
    const rest = trimmed.slice('PRAGMA'.length).trim().toLowerCase().split(/[\s(]/)[0] ?? '';
    if (rest !== 'table_info' && rest !== 'table_list') {
      return { blocked: true, reason: `PRAGMA "${rest}" is not allowed. Only table_info and table_list are permitted` };
    }
  }

  return { blocked: false };
}

// Split on semicolons, filtering out empty statements
function splitStatements(sql: string): string[] {
  return sql
    .split(';')
    .map((s) => s.trim())
    .filter((s) => s.length > 0);
}

// Check if all statements in a multi-statement SQL are SELECT (or allowed non-DML)
function allAreSelect(statements: string[]): boolean {
  return statements.every((s) => firstKeyword(s) === 'SELECT');
}

// Check for any write operations
const WRITE_KEYWORDS = new Set(['INSERT', 'UPDATE', 'DELETE', 'REPLACE', 'UPSERT']);

function isWriteStatement(sql: string): boolean {
  return WRITE_KEYWORDS.has(firstKeyword(sql));
}

// --- Format result as text table ---

function formatTable(rows: Record<string, unknown>[]): string {
  if (rows.length === 0) return '(0 rows)';

  const columns = Object.keys(rows[0]!);
  const colWidths = columns.map((col) => {
    const maxVal = rows.reduce((max, row) => {
      const val = String(row[col] ?? 'NULL');
      return Math.max(max, val.length);
    }, 0);
    return Math.max(col.length, maxVal);
  });

  const header = columns.map((col, i) => col.padEnd(colWidths[i]!)).join(' | ');
  const separator = colWidths.map((w) => '-'.repeat(w)).join('-+-');
  const rowLines = rows.map((row) =>
    columns.map((col, i) => String(row[col] ?? 'NULL').padEnd(colWidths[i]!)).join(' | '),
  );

  const lines = [header, separator, ...rowLines];
  lines.push(`(${rows.length} row${rows.length === 1 ? '' : 's'})`);
  return lines.join('\n');
}

// --- Tool definition ---

const SQLITE_DEF: ToolDef = {
  type: 'function',
  function: {
    name: 'SQLite',
    description: 'SQLite DB にクエリを実行する（edit=false 時は SELECT のみ）。詳細は ReadToolDoc({ name: "SQLite" })。',
    parameters: {
      type: 'object',
      properties: {
        query: { type: 'string', description: 'SQL クエリ' },
        db_path: { type: 'string', description: 'DB ファイルパス (省略時は workspace 内の temp.db)' },
      },
      required: ['query'],
    },
  },
};

export const TOOL_DEFS: Record<string, ToolDef> = {
  SQLite: SQLITE_DEF,
};

// --- Tool execution ---

function executeSQLite(input: Record<string, unknown>, ctx: ToolContext): ToolResult {
  const query = input['query'] as string;
  const dbPathInput = typeof input['db_path'] === 'string' ? input['db_path'] : 'temp.db';

  // Resolve DB path
  let resolvedDb: string;
  try {
    resolvedDb = resolveAndGuard(ctx.workspacePath, dbPathInput);
  } catch (e) {
    return { output: (e as Error).message, isError: true };
  }

  // Split and validate all statements
  const statements = splitStatements(query);
  if (statements.length === 0) {
    return { output: 'Empty query', isError: true };
  }

  // DDL check (always blocked regardless of editAllowed)
  for (const stmt of statements) {
    const { blocked, reason } = isStatementBlocked(stmt);
    if (blocked) {
      return { output: `Forbidden SQL: ${reason}`, isError: true };
    }
  }

  // Read-only mode: only SELECT allowed
  if (!ctx.editAllowed) {
    if (!allAreSelect(statements)) {
      return {
        output: 'Only SELECT queries are allowed when edit mode is disabled',
        isError: true,
      };
    }
  }

  // Write check for non-edit mode (belt-and-suspenders)
  if (!ctx.editAllowed) {
    for (const stmt of statements) {
      if (isWriteStatement(stmt)) {
        return {
          output: 'INSERT/UPDATE/DELETE are not allowed when edit mode is disabled',
          isError: true,
        };
      }
    }
  }

  logger.debug(`[SQLite] db=${resolvedDb} editAllowed=${ctx.editAllowed} statements=${statements.length}`);

  // Open database
  let db: Database.Database;
  try {
    db = new Database(resolvedDb, ctx.editAllowed ? {} : { readonly: true });
  } catch (e) {
    return { output: `Failed to open database: ${(e as Error).message}`, isError: true };
  }

  try {
    // Execute all statements
    // For single SELECT: return formatted table
    // For single write: return changes count
    // For multi-statement: execute all, return combined output

    const outputs: string[] = [];

    for (const stmt of statements) {
      const kw = firstKeyword(stmt);

      if (kw === 'SELECT' || kw === 'PRAGMA') {
        try {
          const rows = db.prepare(stmt).all() as Record<string, unknown>[];
          outputs.push(formatTable(rows));
        } catch (e) {
          return { output: `Query error: ${(e as Error).message}`, isError: true };
        }
      } else if (WRITE_KEYWORDS.has(kw)) {
        try {
          const result = db.prepare(stmt).run();
          outputs.push(`${result.changes} row(s) affected`);
        } catch (e) {
          return { output: `Query error: ${(e as Error).message}`, isError: true };
        }
      } else {
        // CREATE TABLE, CREATE VIEW, etc.
        try {
          db.prepare(stmt).run();
          outputs.push(`OK`);
        } catch (e) {
          return { output: `Query error: ${(e as Error).message}`, isError: true };
        }
      }
    }

    return { output: outputs.join('\n\n'), isError: false };
  } finally {
    db.close();
  }
}

export async function executeTool(
  name: string,
  input: Record<string, unknown>,
  ctx: ToolContext,
): Promise<ToolResult | null> {
  switch (name) {
    case 'SQLite':
      return executeSQLite(input, ctx);
    default:
      return null;
  }
}
