import express from 'express';
import { randomUUID } from 'crypto';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';

import { initVaultwarden, vwGetPassword, vwGetItem, vwListItems, vwListOrgItems, vwCreateLogin, vwUpdatePassword } from './vaultwarden.js';
import { initGitea, giteaListRepos, giteaReadFile, giteaWikiList, giteaWikiRead, giteaWikiWrite, giteaListIssues } from './gitea.js';
import { initHA, haGetState, haListEntities, haCallService, haGetHistory } from './homeassistant.js';
import { initZabbix, zabbixGetProblems, zabbixGetHosts, zabbixGetItems, zabbixGetTriggers } from './zabbix.js';
import { initRadicale, radicaleListCalendars, radicaleListEvents, radicaleGetEvent, radicaleCreateCalendar, radicaleDeleteCalendar, radicalePutEvent, radicaleDeleteEvent, radicaleMoveEvent } from './radicale.js';
import { initKanboard, kbListProjects, kbGetProject, kbListTasks, kbMyTasks, kbGetTask, kbSearchTasks, kbListUsers, kbProjectActivity, kbCreateTask, kbUpdateTask, kbMoveTask, kbChangeTaskStatus, kbAssignTask, kbAddComment, kbCreateSubtask, kbUpdateSubtask, kbRemoveTask, kbRemoveComment } from './kanboard.js';

const PORT = parseInt(process.env.PORT || '3100');

// --- Init services ---
async function init() {
  await initVaultwarden();

  // Fetch all tokens from org to avoid "more than one result" on duplicates
  const orgItems = vwListOrgItems();
  const orgToken = (name) => {
    const item = orgItems.find(i => i.name === name);
    if (!item) throw new Error(`Token not found in org: ${name}`);
    return item.login?.password;
  };

  const gitea_token = orgToken('GITEA_TOKEN');
  const ha_token = orgToken('HA_TOKEN');
  const zabbix_token = orgToken('ZABBIX_TOKEN');
  const radicale_password = orgToken('RADICALE_PASSWORD');
  const kanboard_token = orgToken('KANBOARD_TOKEN');

  initGitea(gitea_token);
  initHA(ha_token);
  initZabbix(zabbix_token);
  initRadicale(radicale_password);
  initKanboard(kanboard_token);

}

// --- MCP Server factory (one per session — McpServer can't share transports) ---
function ok(text) {
  return { content: [{ type: 'text', text: typeof text === 'string' ? text : JSON.stringify(text, null, 2) }] };
}

function err(e) {
  return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
}

function createServer() {
  const server = new McpServer({ name: 'agap-mcp', version: '1.0.0' });

// --- Vaultwarden tools ---
  server.tool('vw_get_password', 'Get password for a Vaultwarden item by name', { name: z.string() },
  async ({ name }) => {
    try { return ok(vwGetPassword(name)); } catch (e) { return err(e); }
  });

  server.tool('vw_get_item', 'Get full details of a Vaultwarden item (name, username, password, url, notes)', { name: z.string() },
  async ({ name }) => {
    try {
      const item = vwGetItem(name);
      return ok({ name: item.name, username: item.login?.username, password: item.login?.password, url: item.login?.uris?.[0]?.uri, notes: item.notes });
    } catch (e) { return err(e); }
  });

  server.tool('vw_list_items', 'List Vaultwarden items. Searches personal vault by default; set org=true to search org/AI collection', {
  search: z.string().optional(),
  org: z.boolean().optional(),
}, async ({ search, org }) => {
  try {
    const items = org ? vwListOrgItems(search) : vwListItems(search);
    return ok(items.map(i => ({ id: i.id, name: i.name, username: i.login?.username, url: i.login?.uris?.[0]?.uri })));
  } catch (e) { return err(e); }
});

  server.tool('vw_create_login', 'Create a new login item in Vaultwarden AI collection', {
  name: z.string(),
  username: z.string().optional(),
  password: z.string(),
  url: z.string().optional(),
  notes: z.string().optional(),
}, async (args) => {
  try { return ok(vwCreateLogin(args)); } catch (e) { return err(e); }
});

  server.tool('vw_update_password', 'Update the password of an existing Vaultwarden item', {
  name: z.string().describe('Item name or ID'),
  password: z.string(),
}, async ({ name, password }) => {
  try { return ok(vwUpdatePassword(name, password)); } catch (e) { return err(e); }
});

// --- Gitea tools ---
  server.tool('gitea_list_repos', 'List all Gitea repositories', {},
  async () => {
    try { return ok(await giteaListRepos()); } catch (e) { return err(e); }
  });

  server.tool('gitea_read_file', 'Read a file from a Gitea repository', {
  repo: z.string().describe('e.g. alvis/AgapHost'),
  path: z.string().describe('file path in repo'),
  ref: z.string().optional().describe('branch/tag/commit, default HEAD'),
}, async ({ repo, path, ref }) => {
  try { return ok(await giteaReadFile(repo, path, ref)); } catch (e) { return err(e); }
});

  server.tool('gitea_wiki_list', 'List wiki pages', {
  repo: z.string().optional().describe('default: alvis/AgapHost'),
}, async ({ repo }) => {
  try { return ok(await giteaWikiList(repo)); } catch (e) { return err(e); }
});

  server.tool('gitea_wiki_read', 'Read a wiki page', {
  page: z.string(),
  repo: z.string().optional().describe('default: alvis/AgapHost'),
}, async ({ page, repo }) => {
  try { return ok(await giteaWikiRead(page, repo)); } catch (e) { return err(e); }
});

  server.tool('gitea_wiki_write', 'Write/update a wiki page', {
  page: z.string(),
  content: z.string(),
  message: z.string().optional().describe('commit message'),
  repo: z.string().optional().describe('default: alvis/AgapHost'),
}, async ({ page, content, message, repo }) => {
  try { return ok(await giteaWikiWrite(page, content, message, repo)); } catch (e) { return err(e); }
});

  server.tool('gitea_list_issues', 'List issues for a Gitea repo', {
  repo: z.string().describe('e.g. alvis/AgapHost'),
  state: z.enum(['open', 'closed', 'all']).optional(),
}, async ({ repo, state }) => {
  try { return ok(await giteaListIssues(repo, state)); } catch (e) { return err(e); }
});

// --- Home Assistant tools ---
  server.tool('ha_get_state', 'Get current state of a Home Assistant entity', {
  entity_id: z.string().describe('e.g. light.living_room'),
}, async ({ entity_id }) => {
  try { return ok(await haGetState(entity_id)); } catch (e) { return err(e); }
});

  server.tool('ha_list_entities', 'List Home Assistant entities, optionally filtered by domain', {
  domain: z.string().optional().describe('e.g. light, switch, sensor, binary_sensor'),
}, async ({ domain }) => {
  try { return ok(await haListEntities(domain)); } catch (e) { return err(e); }
});

  server.tool('ha_call_service', 'Call a Home Assistant service', {
  domain: z.string().describe('e.g. light, switch, automation'),
  service: z.string().describe('e.g. turn_on, turn_off, toggle'),
  data: z.record(z.unknown()).optional().describe('service call data, e.g. {"entity_id": "light.x"}'),
}, async ({ domain, service, data }) => {
  try { return ok(await haCallService(domain, service, data)); } catch (e) { return err(e); }
});

  server.tool('ha_get_history', 'Get state history for a Home Assistant entity', {
  entity_id: z.string(),
  hours: z.number().optional().describe('hours of history, default 24'),
}, async ({ entity_id, hours }) => {
  try { return ok(await haGetHistory(entity_id, hours)); } catch (e) { return err(e); }
});

// --- Zabbix tools ---
  server.tool('zabbix_get_problems', 'Get current active problems in Zabbix', {},
  async () => {
    try { return ok(await zabbixGetProblems()); } catch (e) { return err(e); }
  });

  server.tool('zabbix_get_hosts', 'List all monitored hosts in Zabbix with availability status', {},
  async () => {
    try { return ok(await zabbixGetHosts()); } catch (e) { return err(e); }
  });

  server.tool('zabbix_get_items', 'Get monitored items and their latest values for a Zabbix host', {
  hostid: z.string().describe('Zabbix host ID'),
}, async ({ hostid }) => {
  try { return ok(await zabbixGetItems(hostid)); } catch (e) { return err(e); }
});

  server.tool('zabbix_get_triggers', 'Get triggers for a Zabbix host or all hosts', {
  hostid: z.string().optional().describe('Zabbix host ID, omit for all hosts'),
}, async ({ hostid }) => {
  try { return ok(await zabbixGetTriggers(hostid)); } catch (e) { return err(e); }
});

// --- Radicale (CalDAV) tools ---
  server.tool('radicale_list_calendars', 'List CalDAV calendars for a Radicale user (defaults to alvis)', {
    user: z.string().optional(),
  }, async ({ user }) => {
    try { return ok(await radicaleListCalendars(user)); } catch (e) { return err(e); }
  });

  server.tool('radicale_list_events', 'List events in a Radicale calendar (returns filename, uid, summary, dtstart, dtend)', {
    calendar_id: z.string().describe('Calendar UUID from radicale_list_calendars'),
    user: z.string().optional(),
  }, async ({ calendar_id, user }) => {
    try { return ok(await radicaleListEvents(calendar_id, user)); } catch (e) { return err(e); }
  });

  server.tool('radicale_get_event', 'Get raw iCalendar text for a single event', {
    calendar_id: z.string(),
    filename: z.string().describe('e.g. ABCD-1234.ics'),
    user: z.string().optional(),
  }, async ({ calendar_id, filename, user }) => {
    try { return ok(await radicaleGetEvent(calendar_id, filename, user)); } catch (e) { return err(e); }
  });

  server.tool('radicale_create_calendar', 'Create a new CalDAV calendar (MKCALENDAR). Returns the new calendar id.', {
    displayname: z.string(),
    color: z.string().optional().describe('e.g. #33CC66ff'),
    components: z.array(z.string()).optional().describe('default ["VEVENT"]'),
    id: z.string().optional().describe('explicit collection id; otherwise UUID generated'),
    user: z.string().optional(),
  }, async (args) => {
    try { return ok(await radicaleCreateCalendar(args)); } catch (e) { return err(e); }
  });

  server.tool('radicale_delete_calendar', 'Delete a Radicale calendar collection', {
    calendar_id: z.string(),
    user: z.string().optional(),
  }, async ({ calendar_id, user }) => {
    try { return ok(await radicaleDeleteCalendar(calendar_id, user)); } catch (e) { return err(e); }
  });

  server.tool('radicale_put_event', 'PUT an iCalendar event into a calendar (create or replace)', {
    calendar_id: z.string(),
    filename: z.string().describe('Event filename, with or without .ics'),
    ics: z.string().describe('Full VCALENDAR body'),
    user: z.string().optional(),
  }, async ({ calendar_id, filename, ics, user }) => {
    try { return ok(await radicalePutEvent(calendar_id, filename, ics, user)); } catch (e) { return err(e); }
  });

  server.tool('radicale_delete_event', 'Delete an event from a calendar', {
    calendar_id: z.string(),
    filename: z.string(),
    user: z.string().optional(),
  }, async ({ calendar_id, filename, user }) => {
    try { return ok(await radicaleDeleteEvent(calendar_id, filename, user)); } catch (e) { return err(e); }
  });

  server.tool('radicale_move_event', 'Move an event between calendars (copy then delete original)', {
    from_calendar: z.string(),
    to_calendar: z.string(),
    filename: z.string(),
    user: z.string().optional(),
  }, async ({ from_calendar, to_calendar, filename, user }) => {
    try { return ok(await radicaleMoveEvent({ fromCalendar: from_calendar, toCalendar: to_calendar, filename, user })); } catch (e) { return err(e); }
  });

// --- Kanboard tools ---
  server.tool('kanboard_list_projects', 'List all Kanboard projects (id, name, active, owner)', {},
  async () => {
    try { return ok(await kbListProjects()); } catch (e) { return err(e); }
  });

  server.tool('kanboard_get_project', 'Get a Kanboard project with its columns and swimlanes (needed to move cards)', {
  project_id: z.number().describe('Project ID'),
}, async ({ project_id }) => {
  try { return ok(await kbGetProject(project_id)); } catch (e) { return err(e); }
});

  server.tool('kanboard_list_tasks', 'List tasks in a Kanboard project', {
  project_id: z.number(),
  status: z.enum(['open', 'closed', 'all']).optional().describe('default: open'),
}, async ({ project_id, status }) => {
  try { return ok(await kbListTasks(project_id, status)); } catch (e) { return err(e); }
});

  server.tool('kanboard_my_tasks', "List tasks assigned to the bot user (claude) across all projects", {
  status: z.enum(['open', 'closed', 'all']).optional().describe('default: open'),
}, async ({ status }) => {
  try { return ok(await kbMyTasks(status)); } catch (e) { return err(e); }
});

  server.tool('kanboard_get_task', 'Get full detail of a Kanboard task, including its subtasks and comments', {
  task_id: z.number(),
}, async ({ task_id }) => {
  try { return ok(await kbGetTask(task_id)); } catch (e) { return err(e); }
});

  server.tool('kanboard_search_tasks', 'Search tasks in a project using Kanboard query syntax (e.g. "assignee:claude status:open", "due:today", "color:red")', {
  project_id: z.number(),
  query: z.string(),
}, async ({ project_id, query }) => {
  try { return ok(await kbSearchTasks(project_id, query)); } catch (e) { return err(e); }
});

  server.tool('kanboard_list_users', 'List all Kanboard users (id, username, name, role)', {},
  async () => {
    try { return ok(await kbListUsers()); } catch (e) { return err(e); }
  });

  server.tool('kanboard_project_activity', 'Get the recent activity stream for a project (who did what)', {
  project_id: z.number(),
}, async ({ project_id }) => {
  try { return ok(await kbProjectActivity(project_id)); } catch (e) { return err(e); }
});

  server.tool('kanboard_create_task', 'Create a Kanboard task (authored by claude). At minimum provide title and project_id', {
  title: z.string(),
  project_id: z.number(),
  description: z.string().optional().describe('Markdown'),
  column_id: z.number().optional(),
  owner_id: z.number().optional().describe('Assignee user id'),
  color_id: z.string().optional().describe('e.g. yellow, blue, red, green'),
  date_due: z.string().optional().describe('YYYY-MM-DD'),
  priority: z.number().optional(),
  swimlane_id: z.number().optional(),
}, async (args) => {
  try { return ok(await kbCreateTask(args)); } catch (e) { return err(e); }
});

  server.tool('kanboard_update_task', 'Update fields of a Kanboard task', {
  id: z.number().describe('Task id'),
  title: z.string().optional(),
  description: z.string().optional(),
  owner_id: z.number().optional(),
  color_id: z.string().optional(),
  date_due: z.string().optional().describe('YYYY-MM-DD'),
  priority: z.number().optional(),
  category_id: z.number().optional(),
}, async (args) => {
  try { return ok(await kbUpdateTask(args)); } catch (e) { return err(e); }
});

  server.tool('kanboard_move_task', 'Move a task to a column/position (e.g. to "Work in progress" or "Done"). project_id and swimlane_id are auto-resolved if omitted', {
  task_id: z.number(),
  column_id: z.number().describe('Target column id (see kanboard_get_project)'),
  position: z.number().optional().describe('default 1 (top)'),
  swimlane_id: z.number().optional(),
  project_id: z.number().optional(),
}, async (args) => {
  try { return ok(await kbMoveTask(args)); } catch (e) { return err(e); }
});

  server.tool('kanboard_change_task_status', 'Open or close (mark done/archive) a task', {
  task_id: z.number(),
  action: z.enum(['open', 'close']),
}, async ({ task_id, action }) => {
  try { return ok(await kbChangeTaskStatus(task_id, action)); } catch (e) { return err(e); }
});

  server.tool('kanboard_assign_task', 'Assign a task to a user (username or numeric id). Use "claude" to take it', {
  task_id: z.number(),
  owner: z.union([z.string(), z.number()]).describe('Username or user id'),
}, async ({ task_id, owner }) => {
  try { return ok(await kbAssignTask(task_id, owner)); } catch (e) { return err(e); }
});

  server.tool('kanboard_add_comment', 'Add a comment to a task (authored by claude by default)', {
  task_id: z.number(),
  content: z.string().describe('Markdown'),
  user_id: z.number().optional().describe('Override author; defaults to claude'),
}, async ({ task_id, content, user_id }) => {
  try { return ok(await kbAddComment(task_id, content, user_id)); } catch (e) { return err(e); }
});

  server.tool('kanboard_create_subtask', 'Add a subtask to a task (good for tracking execution steps)', {
  task_id: z.number(),
  title: z.string(),
  user_id: z.number().optional().describe('Assignee; defaults to claude'),
  status: z.number().optional().describe('0=todo, 1=in progress, 2=done'),
}, async (args) => {
  try { return ok(await kbCreateSubtask(args)); } catch (e) { return err(e); }
});

  server.tool('kanboard_update_subtask', 'Update a subtask (e.g. mark in-progress or done)', {
  id: z.number().describe('Subtask id'),
  task_id: z.number(),
  title: z.string().optional(),
  status: z.number().optional().describe('0=todo, 1=in progress, 2=done'),
  user_id: z.number().optional(),
}, async (args) => {
  try { return ok(await kbUpdateSubtask(args)); } catch (e) { return err(e); }
});

  server.tool('kanboard_remove_task', 'Permanently delete a task (irreversible)', {
  task_id: z.number(),
}, async ({ task_id }) => {
  try { return ok(await kbRemoveTask(task_id)); } catch (e) { return err(e); }
});

  server.tool('kanboard_remove_comment', 'Permanently delete a comment (irreversible)', {
  comment_id: z.number(),
}, async ({ comment_id }) => {
  try { return ok(await kbRemoveComment(comment_id)); } catch (e) { return err(e); }
});

  return server;
}

// --- HTTP server (Streamable HTTP + legacy SSE) ---
const app = express();
app.use(express.json());

const sseTransports = new Map();

// Streamable HTTP — stateless: fresh server per request, survives container restarts
app.all('/mcp', async (req, res) => {
  try {
    const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
    res.on('close', () => transport.close());
    await createServer().connect(transport);
    await transport.handleRequest(req, res, req.body);
  } catch (e) {
    console.error('MCP request error:', e.message);
    if (!res.headersSent) {
      res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: e.message }, id: null });
    }
  }
});

// Legacy SSE — kept for backward compatibility
app.get('/sse', async (req, res) => {
  const transport = new SSEServerTransport('/messages', res);
  sseTransports.set(transport.sessionId, transport);
  res.on('close', () => sseTransports.delete(transport.sessionId));
  await createServer().connect(transport);
});

app.post('/messages', async (req, res) => {
  const transport = sseTransports.get(req.query.sessionId);
  if (!transport) return res.status(400).send('Unknown session');
  await transport.handlePostMessage(req, res);
});

app.get('/health', (_, res) => res.json({ status: 'ok', tools: 45 }));

init()
  .then(() => {
    app.listen(PORT, () => console.log(`agap-mcp listening on :${PORT}`));
  })
  .catch(e => {
    console.error('Init failed:', e.message);
    process.exit(1);
  });
