import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
import * as z from 'zod/v4';

const DEFAULT_PORT = 5151;
const DEFAULT_HOST = '127.0.0.1';
const DEFAULT_BACKEND_API_BASE = 'http://127.0.0.1:5150/api';
const DEFAULT_API_KEY = 'termi-mcp-local-dev-key';

const HOST = process.env.TERMI_MCP_HOST || DEFAULT_HOST;
const PORT = Number.parseInt(process.env.TERMI_MCP_PORT || `${DEFAULT_PORT}`, 10);
const API_BASE = (process.env.TERMI_BACKEND_API_BASE || DEFAULT_BACKEND_API_BASE).replace(/\/+$/, '');
const API_KEY = process.env.TERMI_MCP_API_KEY || DEFAULT_API_KEY;

function unauthorizedResponse(res) {
  res.status(401).json({
    error: 'Unauthorized',
    message: 'Missing or invalid MCP API key'
  });
}

function verifyApiKey(req, res, next) {
  const authorization = req.headers.authorization;
  const bearerToken = typeof authorization === 'string' && authorization.startsWith('Bearer ')
    ? authorization.slice('Bearer '.length).trim()
    : null;
  const xApiKey = typeof req.headers['x-api-key'] === 'string' ? req.headers['x-api-key'].trim() : null;
  const token = bearerToken || xApiKey;

  if (!token || token !== API_KEY) {
    unauthorizedResponse(res);
    return;
  }

  next();
}

async function readBackendResponse(response) {
  const rawText = await response.text();

  if (!rawText) {
    if (response.ok) {
      return null;
    }

    throw new Error(`Backend request failed with status ${response.status}`);
  }

  try {
    const parsed = JSON.parse(rawText);
    if (!response.ok) {
      const message = parsed.description || parsed.error || parsed.message || rawText;
      throw new Error(message);
    }
    return parsed;
  } catch (error) {
    if (!response.ok) {
      throw new Error(rawText);
    }

    if (error instanceof SyntaxError) {
      return rawText;
    }

    throw error;
  }
}

async function requestBackend(method, path, body) {
  const response = await fetch(`${API_BASE}${path}`, {
    method,
    headers: {
      Accept: 'application/json',
      ...(body ? { 'Content-Type': 'application/json' } : {})
    },
    body: body ? JSON.stringify(body) : undefined
  });

  return readBackendResponse(response);
}

function createToolResult(title, data) {
  const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
  const structuredContent = typeof data === 'string'
    ? { text: data }
    : Array.isArray(data)
      ? { items: data }
      : data;

  return {
    content: [
      {
        type: 'text',
        text: `${title}\n${text}`
      }
    ],
    structuredContent
  };
}

function buildPostsQuery(args) {
  const query = new URLSearchParams();
  const pairs = [
    ['slug', args.slug],
    ['category', args.category],
    ['tag', args.tag],
    ['search', args.search],
    ['type', args.postType]
  ];

  for (const [key, value] of pairs) {
    if (typeof value === 'string' && value.trim()) {
      query.set(key, value.trim());
    }
  }

  if (typeof args.pinned === 'boolean') {
    query.set('pinned', `${args.pinned}`);
  }

  const serialized = query.toString();
  return serialized ? `?${serialized}` : '';
}

function getServer() {
  const server = new McpServer(
    {
      name: 'termi-blog-mcp',
      version: '0.1.0'
    },
    {
      capabilities: {
        logging: {}
      }
    }
  );

  server.registerTool(
    'posts_list',
    {
      title: 'List Posts',
      description: '列出博客文章，可按 slug、分类、标签、搜索词和类型过滤。',
      inputSchema: {
        slug: z.string().optional(),
        category: z.string().optional(),
        tag: z.string().optional(),
        search: z.string().optional(),
        postType: z.string().optional(),
        pinned: z.boolean().optional()
      }
    },
    async (args) => {
      const data = await requestBackend('GET', `/posts${buildPostsQuery(args)}`);
      return createToolResult('Posts list', data);
    }
  );

  server.registerTool(
    'post_get',
    {
      title: 'Get Post',
      description: '按文章 slug 获取结构化文章信息。',
      inputSchema: {
        slug: z.string().min(1)
      }
    },
    async ({ slug }) => {
      const data = await requestBackend('GET', `/posts/slug/${encodeURIComponent(slug)}`);
      return createToolResult(`Post ${slug}`, data);
    }
  );

  server.registerTool(
    'post_get_markdown',
    {
      title: 'Get Post Markdown',
      description: '读取文章对应的 Markdown 文件内容和路径。',
      inputSchema: {
        slug: z.string().min(1)
      }
    },
    async ({ slug }) => {
      const data = await requestBackend(
        'GET',
        `/posts/slug/${encodeURIComponent(slug)}/markdown`
      );
      return createToolResult(`Post markdown ${slug}`, data);
    }
  );

  server.registerTool(
    'post_create_markdown',
    {
      title: 'Create Markdown Post',
      description: '创建新的 Markdown 文章文件，并同步到博客数据库。',
      inputSchema: {
        title: z.string().min(1),
        slug: z.string().optional(),
        description: z.string().optional(),
        content: z.string().optional(),
        category: z.string().optional(),
        tags: z.array(z.string()).optional(),
        postType: z.string().optional(),
        image: z.string().optional(),
        pinned: z.boolean().optional(),
        published: z.boolean().optional()
      }
    },
    async (args) => {
      const data = await requestBackend('POST', '/posts/markdown', args);
      return createToolResult('Created markdown post', data);
    }
  );

  server.registerTool(
    'post_update_markdown',
    {
      title: 'Update Post Markdown',
      description: '直接更新文章 Markdown 文件内容，适合 frontmatter 或正文整体重写。',
      inputSchema: {
        slug: z.string().min(1),
        markdown: z.string().min(1)
      }
    },
    async ({ slug, markdown }) => {
      const data = await requestBackend(
        'PUT',
        `/posts/slug/${encodeURIComponent(slug)}/markdown`,
        { markdown }
      );
      return createToolResult(`Updated markdown ${slug}`, data);
    }
  );

  server.registerTool(
    'post_delete_markdown',
    {
      title: 'Delete Post Markdown',
      description: '删除文章 Markdown 文件，并同步删除数据库中的文章记录。',
      inputSchema: {
        slug: z.string().min(1)
      }
    },
    async ({ slug }) => {
      const data = await requestBackend(
        'DELETE',
        `/posts/slug/${encodeURIComponent(slug)}/markdown`
      );
      return createToolResult(`Deleted markdown ${slug}`, data);
    }
  );

  server.registerTool(
    'categories_list',
    {
      title: 'List Categories',
      description: '列出所有分类和文章数量。',
      inputSchema: {}
    },
    async () => {
      const data = await requestBackend('GET', '/categories');
      return createToolResult('Categories list', data);
    }
  );

  server.registerTool(
    'category_create',
    {
      title: 'Create Category',
      description: '创建新的分类。',
      inputSchema: {
        name: z.string().min(1),
        slug: z.string().optional()
      }
    },
    async (args) => {
      const data = await requestBackend('POST', '/categories', args);
      return createToolResult('Created category', data);
    }
  );

  server.registerTool(
    'category_update',
    {
      title: 'Update Category',
      description: '更新分类名称或 slug，并同步更新引用它的文章 frontmatter。',
      inputSchema: {
        id: z.number().int().positive(),
        name: z.string().min(1),
        slug: z.string().optional()
      }
    },
    async ({ id, ...payload }) => {
      const data = await requestBackend('PATCH', `/categories/${id}`, payload);
      return createToolResult(`Updated category ${id}`, data);
    }
  );

  server.registerTool(
    'category_delete',
    {
      title: 'Delete Category',
      description: '删除分类，并移除文章 frontmatter 中对该分类的引用。',
      inputSchema: {
        id: z.number().int().positive()
      }
    },
    async ({ id }) => {
      await requestBackend('DELETE', `/categories/${id}`);
      return createToolResult(`Deleted category ${id}`, { id, deleted: true });
    }
  );

  server.registerTool(
    'tags_list',
    {
      title: 'List Tags',
      description: '列出所有标签。',
      inputSchema: {}
    },
    async () => {
      const data = await requestBackend('GET', '/tags');
      return createToolResult('Tags list', data);
    }
  );

  server.registerTool(
    'tag_create',
    {
      title: 'Create Tag',
      description: '创建新的标签。',
      inputSchema: {
        name: z.string().min(1),
        slug: z.string().min(1)
      }
    },
    async (args) => {
      const data = await requestBackend('POST', '/tags', args);
      return createToolResult('Created tag', data);
    }
  );

  server.registerTool(
    'tag_update',
    {
      title: 'Update Tag',
      description: '更新标签名称或 slug，并同步更新文章 frontmatter 里的标签。',
      inputSchema: {
        id: z.number().int().positive(),
        name: z.string().min(1),
        slug: z.string().min(1)
      }
    },
    async ({ id, ...payload }) => {
      const data = await requestBackend('PATCH', `/tags/${id}`, payload);
      return createToolResult(`Updated tag ${id}`, data);
    }
  );

  server.registerTool(
    'tag_delete',
    {
      title: 'Delete Tag',
      description: '删除标签，并从文章 frontmatter 中移除它。',
      inputSchema: {
        id: z.number().int().positive()
      }
    },
    async ({ id }) => {
      await requestBackend('DELETE', `/tags/${id}`);
      return createToolResult(`Deleted tag ${id}`, { id, deleted: true });
    }
  );

  server.registerTool(
    'site_settings_get',
    {
      title: 'Get Site Settings',
      description: '获取前台公开的站点设置。',
      inputSchema: {}
    },
    async () => {
      const data = await requestBackend('GET', '/site_settings');
      return createToolResult('Site settings', data);
    }
  );

  server.registerTool(
    'site_settings_update',
    {
      title: 'Update Site Settings',
      description: '更新站点设置，包括网站资料和 AI 开关等字段。',
      inputSchema: {
        siteName: z.string().optional(),
        siteShortName: z.string().optional(),
        siteUrl: z.string().optional(),
        siteTitle: z.string().optional(),
        siteDescription: z.string().optional(),
        heroTitle: z.string().optional(),
        heroSubtitle: z.string().optional(),
        ownerName: z.string().optional(),
        ownerTitle: z.string().optional(),
        ownerBio: z.string().optional(),
        ownerAvatarUrl: z.string().optional(),
        socialGithub: z.string().optional(),
        socialTwitter: z.string().optional(),
        socialEmail: z.string().optional(),
        location: z.string().optional(),
        techStack: z.array(z.string()).optional(),
        aiEnabled: z.boolean().optional(),
        aiProvider: z.string().optional(),
        aiApiBase: z.string().optional(),
        aiApiKey: z.string().optional(),
        aiChatModel: z.string().optional(),
        aiSystemPrompt: z.string().optional(),
        aiTopK: z.number().int().min(1).max(12).optional(),
        aiChunkSize: z.number().int().min(400).max(4000).optional()
      }
    },
    async (args) => {
      const data = await requestBackend('PATCH', '/site_settings', args);
      return createToolResult('Updated site settings', data);
    }
  );

  server.registerTool(
    'ai_reindex',
    {
      title: 'Rebuild AI Index',
      description: '重建博客 AI 检索索引。',
      inputSchema: {}
    },
    async () => {
      const data = await requestBackend('POST', '/ai/reindex');
      return createToolResult('AI index rebuild job queued', data);
    }
  );

  return server;
}

const app = createMcpExpressApp({ host: HOST });
app.use('/mcp', verifyApiKey);

app.get('/health', (_req, res) => {
  res.json({
    ok: true,
    name: 'termi-blog-mcp',
    host: HOST,
    port: PORT,
    apiBase: API_BASE
  });
});

app.post('/mcp', async (req, res) => {
  const server = getServer();

  try {
    const transport = new StreamableHTTPServerTransport({
      sessionIdGenerator: undefined
    });

    await server.connect(transport);
    await transport.handleRequest(req, res, req.body);

    res.on('close', () => {
      transport.close().catch(() => {});
      server.close().catch(() => {});
    });
  } catch (error) {
    console.error('MCP request failed:', error);

    if (!res.headersSent) {
      res.status(500).json({
        jsonrpc: '2.0',
        error: {
          code: -32603,
          message: error instanceof Error ? error.message : 'Internal server error'
        },
        id: null
      });
    }
  }
});

app.get('/mcp', (_req, res) => {
  res.status(405).json({
    jsonrpc: '2.0',
    error: {
      code: -32000,
      message: 'Method not allowed.'
    },
    id: null
  });
});

app.delete('/mcp', (_req, res) => {
  res.status(405).json({
    jsonrpc: '2.0',
    error: {
      code: -32000,
      message: 'Method not allowed.'
    },
    id: null
  });
});

app.listen(PORT, HOST, (error) => {
  if (error) {
    console.error('Failed to start Termi MCP server:', error);
    process.exit(1);
  }

  console.log(`Termi MCP server listening on http://${HOST}:${PORT}/mcp`);
  console.log(`Backend API base: ${API_BASE}`);
  console.log(`Using API key: ${API_KEY}`);
});
