import { afterEach, describe, expect, it } from 'vitest';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import express from 'express';
import request from 'supertest';
import { Repository } from '../db/repository.js';
import { mountShareApi } from './share-api.js';

function setup(user?: { id: string; role: 'admin' | 'user' }) {
  const tempDir = mkdtempSync(join(tmpdir(), 'share-api-'));
  const repo = new Repository(join(tempDir, 'test.db'));
  const app = express();
  app.use(express.json());

  // Mock user middleware (simulate authenticated user)
  const effectiveUser = user ?? { id: 'user-1', role: 'admin' as const };
  app.use((req, _res, next) => {
    (req as any).user = {
      ...effectiveUser,
      status: 'active',
      orgIds: [],
      defaultVisibility: 'private',
      defaultVisibilityOrgId: null,
    };
    (req as any).isAuthenticated = () => true;
    next();
  });

  mountShareApi(app, repo);
  return { app, repo, tempDir };
}

describe('Share API', () => {
  let tempDir = '';

  afterEach(() => {
    if (tempDir) {
      rmSync(tempDir, { recursive: true, force: true });
      tempDir = '';
    }
  });

  it('POST /api/local/tasks/:id/share generates token', async () => {
    const ctx = setup();
    tempDir = ctx.tempDir;
    const task = await ctx.repo.createLocalTask({ title: 'test', body: 'body' });
    const res = await request(ctx.app).post(`/api/local/tasks/${task.id}/share`);
    expect(res.status).toBe(200);
    expect(res.body.shareToken).toBeTruthy();
    expect(res.body.shareUrl).toContain(res.body.shareToken);
  });

  it('DELETE /api/local/tasks/:id/share removes token', async () => {
    const ctx = setup();
    tempDir = ctx.tempDir;
    const task = await ctx.repo.createLocalTask({ title: 'test', body: 'body' });
    await request(ctx.app).post(`/api/local/tasks/${task.id}/share`);
    const res = await request(ctx.app).delete(`/api/local/tasks/${task.id}/share`);
    expect(res.status).toBe(200);
    expect(res.body.ok).toBe(true);

    const updated = await ctx.repo.getLocalTask(task.id);
    expect(updated?.shareToken).toBeNull();
  });

  it('GET /api/shared/:token returns task info', async () => {
    const ctx = setup();
    tempDir = ctx.tempDir;
    const task = await ctx.repo.createLocalTask({ title: 'shared task', body: 'body' });
    const shareRes = await request(ctx.app).post(`/api/local/tasks/${task.id}/share`);
    const token = shareRes.body.shareToken;

    const res = await request(ctx.app).get(`/api/shared/${token}`);
    expect(res.status).toBe(200);
    expect(res.body.task.title).toBe('shared task');
    // ownerId と workspacePath は非公開
    expect(res.body.task.ownerId).toBeUndefined();
    expect(res.body.task.workspacePath).toBeUndefined();
  });

  it('GET /api/shared/:token returns 404 for unknown token', async () => {
    const ctx = setup();
    tempDir = ctx.tempDir;
    const res = await request(ctx.app).get('/api/shared/nonexistent');
    expect(res.status).toBe(404);
  });

  it('GET /api/shared/:token/comments returns comments', async () => {
    const ctx = setup();
    tempDir = ctx.tempDir;
    const task = await ctx.repo.createLocalTask({ title: 'test', body: 'body' });
    await ctx.repo.addLocalTaskComment(task.id, 'user', 'hello', 'comment');
    const shareRes = await request(ctx.app).post(`/api/local/tasks/${task.id}/share`);
    const token = shareRes.body.shareToken;

    const res = await request(ctx.app).get(`/api/shared/${token}/comments`);
    expect(res.status).toBe(200);
    expect(res.body.comments.length).toBe(1);
  });

  it('GET /api/shared/:token/files lists output files', async () => {
    const ctx = setup();
    tempDir = ctx.tempDir;
    const task = await ctx.repo.createLocalTask({ title: 'test', body: 'body' });
    const wsPath = join(tempDir, 'ws');
    mkdirSync(join(wsPath, 'output'), { recursive: true });
    writeFileSync(join(wsPath, 'output', 'result.md'), '# Result');
    await ctx.repo.updateLocalTask(task.id, { workspacePath: wsPath });

    const shareRes = await request(ctx.app).post(`/api/local/tasks/${task.id}/share`);
    const token = shareRes.body.shareToken;

    const res = await request(ctx.app).get(`/api/shared/${token}/files`);
    expect(res.status).toBe(200);
    expect(res.body.entries.length).toBe(1);
    expect(res.body.entries[0].name).toBe('result.md');
  });

  it('GET /api/shared/:token/files/raw serves file content', async () => {
    const ctx = setup();
    tempDir = ctx.tempDir;
    const task = await ctx.repo.createLocalTask({ title: 'test', body: 'body' });
    const wsPath = join(tempDir, 'ws');
    mkdirSync(join(wsPath, 'output'), { recursive: true });
    writeFileSync(join(wsPath, 'output', 'result.md'), '# Hello');
    await ctx.repo.updateLocalTask(task.id, { workspacePath: wsPath });

    const shareRes = await request(ctx.app).post(`/api/local/tasks/${task.id}/share`);
    const token = shareRes.body.shareToken;

    const res = await request(ctx.app).get(`/api/shared/${token}/files/raw?path=result.md`);
    expect(res.status).toBe(200);
  });

  // --- Cross-user authorization ---

  it('POST /share by non-owner non-admin returns 404', async () => {
    const ctx = setup({ id: 'bob', role: 'user' });
    tempDir = ctx.tempDir;
    // Alice owns the task
    const task = await ctx.repo.createLocalTask({ title: 'alice task', body: 'b', ownerId: 'alice', visibility: 'private' });

    const res = await request(ctx.app).post(`/api/local/tasks/${task.id}/share`);
    expect(res.status).toBe(404);

    // Token was NOT generated
    const after = await ctx.repo.getLocalTask(task.id);
    expect(after?.shareToken).toBeFalsy();
  });

  it('DELETE /share by non-owner non-admin returns 404', async () => {
    const ctx = setup({ id: 'bob', role: 'user' });
    tempDir = ctx.tempDir;
    const task = await ctx.repo.createLocalTask({ title: 'alice task', body: 'b', ownerId: 'alice', visibility: 'private' });
    // Alice already shared the task (simulate by setting token directly via repo)
    await ctx.repo.shareLocalTask(task.id);

    const res = await request(ctx.app).delete(`/api/local/tasks/${task.id}/share`);
    expect(res.status).toBe(404);

    // Token still exists (Bob's DELETE was rejected)
    const after = await ctx.repo.getLocalTask(task.id);
    expect(after?.shareToken).toBeTruthy();
  });

  it('POST /share by owner succeeds', async () => {
    const ctx = setup({ id: 'alice', role: 'user' });
    tempDir = ctx.tempDir;
    const task = await ctx.repo.createLocalTask({ title: 'alice task', body: 'b', ownerId: 'alice', visibility: 'private' });

    const res = await request(ctx.app).post(`/api/local/tasks/${task.id}/share`);
    expect(res.status).toBe(200);
    expect(res.body.shareToken).toBeTruthy();
  });

  it('POST /share by admin succeeds for any task', async () => {
    const ctx = setup({ id: 'admin-1', role: 'admin' });
    tempDir = ctx.tempDir;
    const task = await ctx.repo.createLocalTask({ title: 'alice task', body: 'b', ownerId: 'alice', visibility: 'private' });

    const res = await request(ctx.app).post(`/api/local/tasks/${task.id}/share`);
    expect(res.status).toBe(200);
    expect(res.body.shareToken).toBeTruthy();
  });
});
