import { z } from 'zod';
import { FinanceApiClient } from '../services/api/FinanceApiClient';
import { AppError } from '../utils/AppError';
import type { ToolDefinition, ToolExecutionContext } from './types';
import { findRequestSchema, idSchema, unknownRecordSchema } from './schemas';

const financeApi = new FinanceApiClient();
const defaultListPageSize = 100;
const maxAutoListRows = 2000;

type FindRequest = z.infer<typeof findRequestSchema>;
type FindResponse = { data?: unknown[]; total?: number; [key: string]: unknown } | unknown[];

const billPayloadSchema = z.object({
  id_categoria: z.number().int().positive().optional(),
  id_forma_pagamento: z.number().int().positive().optional(),
  id_conta_bancaria: z.number().int().positive().optional(),
  titulo: z.string().min(1),
  descricao: z.string().optional(),
  valor: z.number().positive(),
  valor_pago: z.number().positive().optional(),
  tipo: z.enum(['D', 'R']),
  status: z.enum(['PE', 'PA']).default('PE'),
  vencimento: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
  data_pagamento: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
  parcelas: z.number().int().positive().optional(),
  frequencia: z.number().int().positive().optional(),
});

const updatePayloadSchema = idSchema.extend({
  data: unknownRecordSchema,
});

function authConfig(context: ToolExecutionContext) {
  return {
    basicAuth: context.basicAuth,
  };
}

function rowsFrom(response: FindResponse): unknown[] {
  if (Array.isArray(response)) {
    return response;
  }

  return response.data ?? [];
}

function totalFrom(response: FindResponse, fallback: number): number {
  if (!Array.isArray(response) && typeof response.total === 'number') {
    return response.total;
  }

  return fallback;
}

function rowIdentity(row: unknown): string {
  if (row && typeof row === 'object' && 'id' in row) {
    return String((row as { id?: unknown }).id);
  }

  return JSON.stringify(row);
}

function sanitizeFinanceData(value: unknown): unknown {
  if (Array.isArray(value)) {
    return value.map((item) => sanitizeFinanceData(item));
  }

  if (!value || typeof value !== 'object') {
    return value;
  }

  const sanitized: Record<string, unknown> = {};

  Object.entries(value as Record<string, unknown>).forEach(([key, item]) => {
    const normalizedKey = key.toLowerCase();

    if (['senha', 'password', 'authorization'].includes(normalizedKey)) {
      return;
    }

    if (normalizedKey === 'usuarios') {
      return;
    }

    sanitized[key] = sanitizeFinanceData(item);
  });

  return sanitized;
}

function stripUserFields<T extends Record<string, unknown>>(data: T): Record<string, unknown> {
  const sanitized = { ...data };
  delete sanitized.id_usuario;
  delete sanitized.id_user;
  delete sanitized.usuario;
  delete sanitized.user;
  return sanitized;
}

function withAuthenticatedUser<T extends Record<string, unknown>>(data: T, context: ToolExecutionContext): Record<string, unknown> {
  return {
    ...stripUserFields(data),
    id_usuario: context.user.id,
  };
}

function withAuthenticatedUserFilter(
  input: FindRequest,
  context: ToolExecutionContext,
  defaultFilter: Record<string, unknown> = {},
) {
  return {
    ...input,
    filter: {
      ...defaultFilter,
      ...(input.filter ?? {}),
      id_usuario: context.user.id,
    },
  };
}

async function completeFind(
  path: string,
  input: FindRequest,
  context: ToolExecutionContext,
  defaultFilter: Record<string, unknown> = {},
): Promise<unknown> {
  const { limit: _ignoredLimit, offset: _ignoredOffset, ...findInput } = input;
  const request = withAuthenticatedUserFilter(findInput, context, defaultFilter);
  const firstRequest = {
    ...request,
    limit: defaultListPageSize,
    offset: 0,
  };

  const firstResponse = await financeApi.post<FindResponse>(path, firstRequest, authConfig(context));
  const firstRows = rowsFrom(firstResponse);
  const total = totalFrom(firstResponse, firstRows.length);

  if (Array.isArray(firstResponse) || firstRows.length >= total || firstRows.length === 0) {
    if (Array.isArray(firstResponse)) {
      return sanitizeFinanceData(firstResponse);
    }

    return sanitizeFinanceData({
      ...firstResponse,
      returned: firstRows.length,
      complete: firstRows.length >= total,
      query: request,
    });
  }

  const rows = [...firstRows];
  const seen = new Set(firstRows.map(rowIdentity));
  let offset = firstRows.length;

  while (rows.length < total && rows.length < maxAutoListRows) {
    const response = await financeApi.post<FindResponse>(
      path,
      {
        ...request,
        limit: defaultListPageSize,
        offset,
      },
      authConfig(context),
    );
    const nextRows = rowsFrom(response);

    if (nextRows.length === 0) {
      break;
    }

    const uniqueRows = nextRows.filter((row) => {
      const identity = rowIdentity(row);

      if (seen.has(identity)) {
        return false;
      }

      seen.add(identity);
      return true;
    });

    if (uniqueRows.length === 0) {
      break;
    }

    rows.push(...uniqueRows);
    offset += nextRows.length;
  }

  return sanitizeFinanceData({
    total,
    returned: rows.length,
    complete: rows.length >= total,
    data: rows,
    query: request,
  });
}

async function ensureOwned(listPath: string, id: number, context: ToolExecutionContext, label: string): Promise<void> {
  const response = await financeApi.post<FindResponse>(
    listPath,
    {
      filter: {
        id,
        id_usuario: context.user.id,
      },
      limit: 1,
    },
    authConfig(context),
  );

  if (rowsFrom(response).length === 0) {
    throw new AppError(`${label} not found for authenticated user.`, 404, 'RESOURCE_NOT_FOUND_FOR_USER', {
      id,
      resource: label,
    });
  }
}

async function ensureOptionalOwned(
  listPath: string,
  id: unknown,
  context: ToolExecutionContext,
  label: string,
): Promise<void> {
  if (typeof id === 'number') {
    await ensureOwned(listPath, id, context, label);
  }
}

async function validateBillRelations(data: Record<string, unknown>, context: ToolExecutionContext): Promise<void> {
  await ensureOptionalOwned('/private/categorias/listar', data.id_categoria, context, 'categoria');
  await ensureOptionalOwned('/private/contas-bancarias/listar', data.id_conta_bancaria, context, 'conta bancaria');
}

const resourceTool = {
  list: (name: string, description: string, path: string): ToolDefinition =>
    ({
      name,
      description,
      parameters: findRequestSchema,
      parametersDescription: {
        filter: 'Filtros da API principal. id_usuario sempre sera forçado para o usuario autenticado.',
        limit: 'Use apenas quando o usuario pedir uma quantidade limitada explicitamente.',
        offset: 'Use apenas quando o usuario pedir continuacao ou paginacao explicitamente.',
        order: '{ cols: string[], direction: ASC|DESC }',
        includes: 'Relacionamentos opcionais suportados pelo backend.',
      },
      execute: (input, context) => completeFind(path, input, context, { deletado: 'N' }),
    }) satisfies ToolDefinition,

  create: (name: string, description: string, path: string, schema: z.ZodType): ToolDefinition =>
    ({
      name,
      description,
      parameters: schema,
      parametersDescription: {
        payload: 'Campos de criacao. id_usuario e sempre definido pelo usuario autenticado.',
      },
      execute: (input, context) => financeApi.post(path, withAuthenticatedUser(input, context), authConfig(context)),
    }) satisfies ToolDefinition,

  update: (name: string, description: string, listPath: string, updatePath: string): ToolDefinition =>
    ({
      name,
      description,
      parameters: updatePayloadSchema,
      parametersDescription: {
        id: 'ID do registro do usuario autenticado.',
        data: 'Campos a atualizar. id_usuario informado sera ignorado.',
      },
      execute: async (input: { id: number; data: Record<string, unknown> }, context) => {
        await ensureOwned(listPath, input.id, context, name);
        return financeApi.put(`${updatePath}/${input.id}`, stripUserFields(input.data), authConfig(context));
      },
    }) satisfies ToolDefinition,

  softDelete: (name: string, description: string, listPath: string, updatePath: string): ToolDefinition =>
    ({
      name,
      description,
      parameters: idSchema,
      parametersDescription: {
        id: 'ID do registro do usuario autenticado que deve ser marcado como deletado.',
      },
      execute: async (input: { id: number }, context) => {
        await ensureOwned(listPath, input.id, context, name);
        return financeApi.put(`${updatePath}/${input.id}`, { deletado: 'S' }, authConfig(context));
      },
    }) satisfies ToolDefinition,
};

export const financeTools: ToolDefinition[] = [
  {
    name: 'getCurrentUser',
    description: 'Retorna o usuario autenticado no agente.',
    parameters: z.object({}),
    parametersDescription: {},
    execute: async (_input, context) => ({
      id: context.user.id,
      nome: context.user.nome,
      email: context.user.email,
    }),
  },

  resourceTool.create(
    'createCategory',
    'Cria uma categoria financeira para o usuario autenticado.',
    '/private/categorias/criar',
    z.object({
      descricao: z.string().min(1),
    }),
  ),
  resourceTool.list('listCategories', 'Lista categorias financeiras do usuario autenticado.', '/private/categorias/listar'),
  resourceTool.update('updateCategory', 'Atualiza uma categoria do usuario autenticado.', '/private/categorias/listar', '/private/categorias/atualizar'),
  resourceTool.softDelete('softDeleteCategory', 'Marca uma categoria do usuario autenticado como deletada.', '/private/categorias/listar', '/private/categorias/atualizar'),

  resourceTool.create(
    'createBankAccount',
    'Cria uma conta bancaria/carteira para o usuario autenticado.',
    '/private/contas-bancarias/criar',
    z.object({
      descricao: z.string().min(1),
      observacoes: z.string().default(''),
      saldo: z.number().default(0),
      principal: z.enum(['S', 'N']).default('N'),
    }),
  ),
  resourceTool.list('listBankAccounts', 'Lista contas bancarias/carteiras do usuario autenticado.', '/private/contas-bancarias/listar'),
  resourceTool.update('updateBankAccount', 'Atualiza uma conta bancaria/carteira do usuario autenticado.', '/private/contas-bancarias/listar', '/private/contas-bancarias/atualizar'),
  resourceTool.softDelete('softDeleteBankAccount', 'Marca uma conta bancaria do usuario autenticado como deletada.', '/private/contas-bancarias/listar', '/private/contas-bancarias/atualizar'),

  {
    name: 'createBill',
    description: 'Cria conta financeira, receita ou despesa para o usuario autenticado.',
    parameters: billPayloadSchema,
    parametersDescription: {
      titulo: 'Titulo da conta.',
      valor: 'Valor.',
      tipo: 'D para despesa, R para receita.',
      status: 'PE ou PA.',
      vencimento: 'Data YYYY-MM-DD.',
      id_categoria: 'Opcional, deve pertencer ao usuario autenticado.',
      id_conta_bancaria: 'Opcional, deve pertencer ao usuario autenticado.',
    },
    execute: async (input, context) => {
      await validateBillRelations(input, context);
      return financeApi.post('/private/contas/criar', withAuthenticatedUser(input, context), authConfig(context));
    },
  },
  resourceTool.list('listBills', 'Lista contas, receitas ou despesas do usuario autenticado.', '/private/contas/listar'),
  {
    name: 'updateBill',
    description: 'Atualiza uma conta, receita ou despesa do usuario autenticado.',
    parameters: updatePayloadSchema,
    parametersDescription: {
      id: 'ID da conta do usuario autenticado.',
      data: 'Campos a atualizar. id_usuario informado sera ignorado.',
    },
    execute: async (input: { id: number; data: Record<string, unknown> }, context) => {
      await ensureOwned('/private/contas/listar', input.id, context, 'conta');
      await validateBillRelations(input.data, context);
      return financeApi.put(`/private/contas/atualizar/${input.id}`, stripUserFields(input.data), authConfig(context));
    },
  },
  resourceTool.softDelete('softDeleteBill', 'Marca uma conta financeira do usuario autenticado como deletada.', '/private/contas/listar', '/private/contas/atualizar'),
  {
    name: 'payBill',
    description: 'Efetua pagamento de uma conta do usuario autenticado.',
    parameters: z.object({
      id: z.number().int().positive(),
      valor_pago: z.number().positive(),
      id_forma_pagamento: z.number().int().positive(),
      id_conta_bancaria: z.number().int().positive().optional(),
      data_pagamento: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
    }),
    parametersDescription: {
      id: 'ID da conta do usuario autenticado.',
      valor_pago: 'Valor pago.',
      id_forma_pagamento: 'Forma de pagamento.',
      id_conta_bancaria: 'Conta bancaria opcional do usuario autenticado.',
      data_pagamento: 'Data no formato YYYY-MM-DD. Opcional.',
    },
    execute: async (input, context) => {
      await ensureOwned('/private/contas/listar', input.id, context, 'conta');
      await ensureOptionalOwned('/private/contas-bancarias/listar', input.id_conta_bancaria, context, 'conta bancaria');

      return financeApi.put(
        `/private/contas/atualizar/${input.id}`,
        {
          status: 'PA',
          valor_pago: input.valor_pago,
          id_forma_pagamento: input.id_forma_pagamento,
          id_conta_bancaria: input.id_conta_bancaria,
          data_pagamento: input.data_pagamento,
        },
        authConfig(context),
      );
    },
  },

  {
    name: 'listPaymentForms',
    description: 'Lista formas de pagamento disponiveis.',
    parameters: z.object({}),
    parametersDescription: {},
    execute: () => financeApi.get('/payments-forms'),
  },
  {
    name: 'getGeneralSummary',
    description: 'Consulta resumo financeiro geral de um periodo para o usuario autenticado.',
    parameters: z.object({
      inicio: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
      fim: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
    }),
    parametersDescription: {
      inicio: 'Data inicial YYYY-MM-DD.',
      fim: 'Data final YYYY-MM-DD.',
    },
    execute: (input, context) =>
      financeApi.post(
        '/private/resumos/geral',
        {
          inicio: input.inicio,
          fim: input.fim,
          usuario: context.user.id,
        },
        authConfig(context),
      ),
  },
  {
    name: 'getAnnualSummary',
    description: 'Consulta resumo financeiro anual do usuario autenticado.',
    parameters: z.object({
      ano: z.number().int().min(2000).max(2100),
    }),
    parametersDescription: {
      ano: 'Ano com quatro digitos.',
    },
    execute: (input, context) =>
      financeApi.post(
        '/private/resumos/anual',
        {
          ano: input.ano,
          usuario: context.user.id,
        },
        authConfig(context),
      ),
  },
  {
    name: 'getBalance',
    description: 'Consulta saldo atual de contas bancarias/carteiras do usuario autenticado. Nao use para previsao mensal sozinho.',
    parameters: z.object({
      principal: z.enum(['S', 'N']).optional(),
    }),
    parametersDescription: {
      principal: 'Opcional: S para conta principal, N para nao principal.',
    },
    execute: (input, context) =>
      completeFind(
        '/private/contas-bancarias/listar',
        {
          filter: {
            ...(input.principal ? { principal: input.principal } : {}),
          },
          order: {
            cols: ['principal', 'descricao'],
            direction: 'DESC',
          },
        },
        context,
        { deletado: 'N' },
      ),
  },
  {
    name: 'getFinancialSnapshot',
    description:
      'Busca resumo, contas do periodo e contas bancarias do usuario autenticado para analises, previsoes, saldo previsto e resultado apos receber/pagar.',
    parameters: z.object({
      inicio: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
      fim: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
    }),
    parametersDescription: {
      inicio: 'Data inicial YYYY-MM-DD.',
      fim: 'Data final YYYY-MM-DD.',
    },
    execute: async (input, context) => {
      const [summary, bills, bankAccounts, categories] = await Promise.all([
        financeApi.post(
          '/private/resumos/geral',
          {
            inicio: input.inicio,
            fim: input.fim,
            usuario: context.user.id,
          },
          authConfig(context),
        ),
        completeFind(
          '/private/contas/listar',
          {
            filter: {
              vencimento: {
                BETWEEN: [input.inicio, input.fim],
              },
            },
            order: {
              cols: ['vencimento'],
              direction: 'ASC',
            },
          },
          context,
          { deletado: 'N' },
        ),
        completeFind(
          '/private/contas-bancarias/listar',
          {
            filter: {},
          },
          context,
          { deletado: 'N' },
        ),
        completeFind(
          '/private/categorias/listar',
          {
            filter: {},
          },
          context,
          { deletado: 'N' },
        ),
      ]);

      return {
        summary,
        bills,
        bankAccounts,
        categories,
      };
    },
  },

  {
    name: 'createCard',
    description: 'Cria um card de kanban/tarefas para o usuario autenticado.',
    parameters: z.object({
      titulo: z.string().min(1),
      ordem: z.number().int(),
      tarefas: z.array(unknownRecordSchema).optional(),
    }),
    parametersDescription: {
      titulo: 'Titulo do card.',
      ordem: 'Ordem do card.',
      tarefas: 'Tarefas iniciais opcionais; id_usuario sera forçado para o usuario autenticado.',
    },
    execute: (input, context) =>
      financeApi.post(
        '/private/cards/criar',
        {
          ...withAuthenticatedUser(input, context),
          tarefas: input.tarefas?.map((task: Record<string, unknown>) => withAuthenticatedUser(task, context)),
        },
        authConfig(context),
      ),
  },
  resourceTool.list('listCards', 'Lista cards de kanban do usuario autenticado.', '/private/cards/listar'),
  resourceTool.update('updateCard', 'Atualiza card de kanban do usuario autenticado.', '/private/cards/listar', '/private/cards/atualizar'),
  resourceTool.softDelete('softDeleteCard', 'Marca card do usuario autenticado como deletado.', '/private/cards/listar', '/private/cards/atualizar'),

  {
    name: 'createTask',
    description: 'Cria uma tarefa para o usuario autenticado em um card dele.',
    parameters: z.object({
      id_card: z.number().int().positive(),
      titulo: z.string().min(1),
      descricao: z.string().optional(),
    }),
    parametersDescription: {
      id_card: 'ID do card do usuario autenticado.',
      titulo: 'Titulo da tarefa.',
      descricao: 'Descricao opcional.',
    },
    execute: async (input, context) => {
      await ensureOwned('/private/cards/listar', input.id_card, context, 'card');
      return financeApi.post('/private/tarefas/criar', withAuthenticatedUser(input, context), authConfig(context));
    },
  },
  resourceTool.list('listTasks', 'Lista tarefas do usuario autenticado.', '/private/tarefas/listar'),
  resourceTool.update('updateTask', 'Atualiza tarefa do usuario autenticado.', '/private/tarefas/listar', '/private/tarefas/atualizar'),
  resourceTool.softDelete('softDeleteTask', 'Marca tarefa do usuario autenticado como deletada.', '/private/tarefas/listar', '/private/tarefas/atualizar'),

  resourceTool.create(
    'createNote',
    'Cria uma nota para o usuario autenticado.',
    '/private/notas/criar',
    z.object({
      titulo: z.string().min(1),
      texto: z.string().min(1),
      arquivado: z.enum(['S', 'N']).default('N'),
    }),
  ),
  resourceTool.list('listNotes', 'Lista notas do usuario autenticado.', '/private/notas/listar'),
  resourceTool.update('updateNote', 'Atualiza nota do usuario autenticado.', '/private/notas/listar', '/private/notas/atualizar'),
  resourceTool.softDelete('softDeleteNote', 'Marca nota do usuario autenticado como deletada.', '/private/notas/listar', '/private/notas/atualizar'),
];
