import { Type } from '@sinclair/typebox';
import { AgentToolWithMeta, jsonResult } from '../common.js';
import { registerSchema, TOOLSET_MYSQL } from '../catalog.js';
import { readToolRuntimeContext } from '../runtime_context.js';
import { NetaClawDataSourceService } from '../../service/data_source.js';
import { MysqlIntrospectionService } from '../../service/mysql_schema.js';
import { MysqlQueryService } from '../../service/mysql_query.js';

const RuntimeParam = Type.Optional(Type.Any());

const MysqlListSourcesParams = Type.Object({
  _netaRuntime: RuntimeParam,
});

const MysqlSchemaParams = Type.Object({
  source: Type.String({ description: 'Authorized MySQL data source name.' }),
  tables: Type.Optional(Type.Array(Type.String(), { description: 'Optional table names to inspect.' })),
  _netaRuntime: RuntimeParam,
});

const MysqlTableSampleParams = Type.Object({
  source: Type.String({ description: 'Authorized MySQL data source name.' }),
  table: Type.String({ description: 'Table name to sample.' }),
  columns: Type.Optional(Type.Array(Type.String(), { description: 'Optional columns to return.' })),
  limit: Type.Optional(Type.Number({ description: 'Maximum sample rows.' })),
  _netaRuntime: RuntimeParam,
});

const MysqlQueryParams = Type.Object({
  source: Type.String({ description: 'Authorized MySQL data source name.' }),
  sql: Type.String({ description: 'Read-only SELECT SQL.' }),
  _netaRuntime: RuntimeParam,
});

export interface MysqlToolDeps {
  dataSourceService: NetaClawDataSourceService;
  mysqlIntrospection: MysqlIntrospectionService;
  mysqlQuery: MysqlQueryService;
}

function getRuntimeIdentity(params: Record<string, unknown>): { agentId: number; userId: number | null } {
  const runtime = readToolRuntimeContext(params);
  const agentId = runtime?.currentAgent?.id;
  if (!Number.isFinite(agentId)) {
    throw new Error('current_agent_id_missing');
  }

  return {
    agentId: Number(agentId),
    userId: normalizeOptionalNumber(runtime?.userId),
  };
}

function normalizeOptionalNumber(value: unknown): number | null {
  if (value === undefined || value === null || value === '') {
    return null;
  }
  const numeric = Number(value);
  return Number.isFinite(numeric) ? Math.trunc(numeric) : null;
}

function normalizeStringList(value: unknown): string[] | undefined {
  if (!Array.isArray(value)) {
    return undefined;
  }
  return value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0);
}

export function createMysqlListSourcesTool(deps: Pick<MysqlToolDeps, 'dataSourceService'>): AgentToolWithMeta<typeof MysqlListSourcesParams, unknown> {
  return {
    name: 'mysql_list_sources',
    label: 'List MySQL sources',
    description: 'List MySQL data sources authorized for the current agent.',
    parameters: MysqlListSourcesParams,
    visibility: 'tool',
    async execute(_id, params) {
      const { agentId } = getRuntimeIdentity(params as Record<string, unknown>);
      const sources = await deps.dataSourceService.listForAgent(agentId);
      return jsonResult({ sources });
    },
  };
}

export function createMysqlSchemaTool(deps: Pick<MysqlToolDeps, 'dataSourceService' | 'mysqlIntrospection'>): AgentToolWithMeta<typeof MysqlSchemaParams, unknown> {
  return {
    name: 'mysql_schema',
    label: 'MySQL schema',
    description: 'Inspect authorized MySQL schema metadata.',
    parameters: MysqlSchemaParams,
    visibility: 'tool',
    async execute(_id, params) {
      const { agentId } = getRuntimeIdentity(params as Record<string, unknown>);
      const source = await deps.dataSourceService.getAuthorizedSource(params.source, agentId);
      const schema = await deps.mysqlIntrospection.listSchema(source, {
        tables: normalizeStringList(params.tables),
      });
      return jsonResult(schema);
    },
  };
}

export function createMysqlTableSampleTool(deps: Pick<MysqlToolDeps, 'dataSourceService' | 'mysqlIntrospection'>): AgentToolWithMeta<typeof MysqlTableSampleParams, unknown> {
  return {
    name: 'mysql_table_sample',
    label: 'MySQL table sample',
    description: 'Read a small sample from an authorized MySQL table.',
    parameters: MysqlTableSampleParams,
    visibility: 'tool',
    async execute(_id, params) {
      const { agentId } = getRuntimeIdentity(params as Record<string, unknown>);
      const source = await deps.dataSourceService.getAuthorizedSource(params.source, agentId);
      const sample = await deps.mysqlIntrospection.sampleTable(source, {
        table: params.table,
        columns: normalizeStringList(params.columns),
        limit: params.limit,
      });
      return jsonResult(sample);
    },
  };
}

export function createMysqlQueryTool(deps: Pick<MysqlToolDeps, 'dataSourceService' | 'mysqlQuery'>): AgentToolWithMeta<typeof MysqlQueryParams, unknown> {
  return {
    name: 'mysql_query',
    label: 'MySQL query',
    description: 'Execute a guarded read-only query against an authorized MySQL data source.',
    parameters: MysqlQueryParams,
    visibility: 'tool',
    async execute(id, params) {
      const { agentId, userId } = getRuntimeIdentity(params as Record<string, unknown>);
      const source = await deps.dataSourceService.getAuthorizedSource(params.source, agentId);
      const result = await deps.mysqlQuery.executeReadOnly({
        source,
        sql: params.sql,
        agentId,
        userId,
        toolCallId: id,
      });
      return jsonResult(result);
    },
  };
}

const mysqlSchemas = [
  {
    name: 'mysql_list_sources',
    description: 'List MySQL data sources authorized for the current agent.',
  },
  {
    name: 'mysql_schema',
    description: 'Inspect authorized MySQL schema metadata.',
  },
  {
    name: 'mysql_table_sample',
    description: 'Read a small sample from an authorized MySQL table.',
  },
  {
    name: 'mysql_query',
    description: 'Execute a guarded read-only query against an authorized MySQL data source.',
  },
] as const;

for (const schema of mysqlSchemas) {
  registerSchema({
    ...schema,
    toolset: TOOLSET_MYSQL,
    capability: 'text',
    visibility: 'tool',
    isCore: false,
    canDisable: true,
  });
}
