import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { tmpdir } from 'node:os';
import { parse as parseYaml } from 'yaml';

vi.mock('../infra/task/summarize.js', async (importOriginal) => ({
  ...(await importOriginal<Record<string, unknown>>()),
  summarizeTaskName: vi.fn().mockImplementation((content: string) => {
    const slug = content.split('\n')[0]!
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, '-')
      .replace(/^-+|-+$/g, '')
      .slice(0, 30)
      .replace(/-+$/, '');
    return Promise.resolve(slug || 'task');
  }),
}));

vi.mock('../shared/ui/index.js', () => ({
  success: vi.fn(),
  info: vi.fn(),
  blankLine: vi.fn(),
}));

vi.mock('../shared/prompt/index.js', () => ({
  confirm: vi.fn(),
  promptInput: vi.fn(),
}));

vi.mock('../shared/utils/index.js', async (importOriginal) => ({
  ...(await importOriginal<Record<string, unknown>>()),
  createLogger: () => ({
    info: vi.fn(),
    debug: vi.fn(),
    error: vi.fn(),
  }),
}));

vi.mock('../infra/task/index.js', async (importOriginal) => ({
  ...(await importOriginal<Record<string, unknown>>()),
  getCurrentBranch: vi.fn().mockReturnValue('main'),
  branchExists: vi.fn().mockReturnValue(true),
}));

import { success, info } from '../shared/ui/index.js';
import { confirm, promptInput } from '../shared/prompt/index.js';
import { saveTaskFile, saveTaskFromInteractive } from '../features/tasks/add/index.js';
import { getCurrentBranch, branchExists } from '../infra/task/index.js';

const mockSuccess = vi.mocked(success);
const mockInfo = vi.mocked(info);
const mockConfirm = vi.mocked(confirm);
const mockPromptInput = vi.mocked(promptInput);
const mockGetCurrentBranch = vi.mocked(getCurrentBranch);
const mockBranchExists = vi.mocked(branchExists);

let testDir: string;

function loadTasks(testDir: string): { tasks: Array<Record<string, unknown>> } {
  const raw = fs.readFileSync(path.join(testDir, '.takt', 'tasks.yaml'), 'utf-8');
  return parseYaml(raw) as { tasks: Array<Record<string, unknown>> };
}

beforeEach(() => {
  vi.clearAllMocks();
  vi.useFakeTimers();
  vi.setSystemTime(new Date('2026-02-10T04:40:00.000Z'));
  testDir = fs.mkdtempSync(path.join(tmpdir(), 'takt-test-save-'));
  mockGetCurrentBranch.mockReturnValue('main');
  mockBranchExists.mockReturnValue(true);
});

afterEach(() => {
  vi.useRealTimers();
  if (testDir && fs.existsSync(testDir)) {
    fs.rmSync(testDir, { recursive: true });
  }
});

describe('saveTaskFile', () => {
  it('should append task to tasks.yaml', async () => {
    const created = await saveTaskFile(testDir, 'Implement feature X\nDetails here');

    expect(created.taskName).toContain('implement-feature-x');
    expect(created.tasksFile).toBe(path.join(testDir, '.takt', 'tasks.yaml'));
    expect(fs.existsSync(created.tasksFile)).toBe(true);

    const tasks = loadTasks(testDir).tasks;
    expect(tasks).toHaveLength(1);
    expect(tasks[0]?.content).toBeUndefined();
    expect(tasks[0]?.task_dir).toBeTypeOf('string');
    expect(tasks[0]?.slug).toBeTypeOf('string');
    expect(tasks[0]?.summary).toBe('Implement feature X');
    const taskDir = path.join(testDir, String(tasks[0]?.task_dir));
    expect(fs.existsSync(path.join(taskDir, 'order.md'))).toBe(true);
    expect(fs.readFileSync(path.join(taskDir, 'order.md'), 'utf-8')).toContain('Implement feature X');
  });

  it('should include optional fields', async () => {
    await saveTaskFile(testDir, 'Task', {
      piece: 'review',
      issue: 42,
      worktree: true,
      branch: 'feat/my-branch',
      autoPr: false,
    });

    const task = loadTasks(testDir).tasks[0]!;
    expect(task.piece).toBe('review');
    expect(task.issue).toBe(42);
    expect(task.worktree).toBe(true);
    expect(task.branch).toBe('feat/my-branch');
    expect(task.auto_pr).toBe(false);
    expect(task.task_dir).toBeTypeOf('string');
  });

  it('should persist base_branch when it is provided', async () => {
    await saveTaskFile(testDir, 'Task', {
      piece: 'review',
      issue: 42,
      worktree: true,
      branch: 'feature/bugfix',
      baseBranch: 'release/main',
    });

    const task = loadTasks(testDir).tasks[0]!;
    expect(task.base_branch).toBe('release/main');
  });

  it('should persist draft_pr when draftPr is true', async () => {
    await saveTaskFile(testDir, 'Draft task', {
      autoPr: true,
      draftPr: true,
    });

    const task = loadTasks(testDir).tasks[0]!;
    expect(task.auto_pr).toBe(true);
    expect(task.draft_pr).toBe(true);
  });

  it('should generate unique names on duplicates', async () => {
    const first = await saveTaskFile(testDir, 'Same title');
    const second = await saveTaskFile(testDir, 'Same title');

    expect(first.taskName).not.toBe(second.taskName);

    const tasks = loadTasks(testDir).tasks;
    expect(tasks).toHaveLength(2);
    expect(tasks[0]?.task_dir).toBe('.takt/tasks/20260210-044000-same-title');
    expect(tasks[1]?.task_dir).toBe('.takt/tasks/20260210-044000-same-title-2');
    expect(fs.readFileSync(path.join(testDir, String(tasks[0]?.task_dir), 'order.md'), 'utf-8')).toContain('Same title');
    expect(fs.readFileSync(path.join(testDir, String(tasks[1]?.task_dir), 'order.md'), 'utf-8')).toContain('Same title');
  });
});

describe('saveTaskFromInteractive', () => {
  it('should always save task with worktree settings', async () => {
    mockPromptInput.mockResolvedValueOnce('');
    mockPromptInput.mockResolvedValueOnce('');
    mockConfirm.mockResolvedValueOnce(true);  // auto-create PR?
    mockConfirm.mockResolvedValueOnce(true);  // create as draft?

    await saveTaskFromInteractive(testDir, 'Task content');

    expect(mockSuccess).toHaveBeenCalledWith(expect.stringContaining('Task created:'));
    const task = loadTasks(testDir).tasks[0]!;
    expect(task.worktree).toBe(true);
    expect(task.auto_pr).toBe(true);
    expect(task.draft_pr).toBe(true);
  });

  it('should keep worktree enabled even when auto-pr is declined', async () => {
    mockPromptInput.mockResolvedValueOnce('');
    mockPromptInput.mockResolvedValueOnce('');
    mockConfirm.mockResolvedValueOnce(false);

    await saveTaskFromInteractive(testDir, 'Task content');

    const task = loadTasks(testDir).tasks[0]!;
    expect(task.worktree).toBe(true);
    expect(task.branch).toBeUndefined();
    expect(task.auto_pr).toBe(false);
  });

  it('should display piece info when specified', async () => {
    mockPromptInput.mockResolvedValueOnce('');
    mockPromptInput.mockResolvedValueOnce('');
    mockConfirm.mockResolvedValueOnce(false);

    await saveTaskFromInteractive(testDir, 'Task content', 'review');

    expect(mockInfo).toHaveBeenCalledWith('  Piece: review');
  });

  it('should record issue number in tasks.yaml when issue option is provided', async () => {
    mockPromptInput.mockResolvedValueOnce('');
    mockPromptInput.mockResolvedValueOnce('');
    mockConfirm.mockResolvedValueOnce(false);

    await saveTaskFromInteractive(testDir, 'Fix login bug', 'default', { issue: 42 });

    const task = loadTasks(testDir).tasks[0]!;
    expect(task.issue).toBe(42);
  });

  describe('with confirmAtEndMessage', () => {
    it('should not save task when user declines confirmAtEndMessage', async () => {
      mockConfirm.mockResolvedValueOnce(false);

      await saveTaskFromInteractive(testDir, 'Task content', 'default', {
        issue: 42,
        confirmAtEndMessage: 'Add this issue to tasks?',
      });

      expect(fs.existsSync(path.join(testDir, '.takt', 'tasks.yaml'))).toBe(false);
    });

    it('should prompt worktree settings after confirming confirmAtEndMessage', async () => {
      mockConfirm.mockResolvedValueOnce(true);
      mockPromptInput.mockResolvedValueOnce('');
      mockPromptInput.mockResolvedValueOnce('');
      mockConfirm.mockResolvedValueOnce(false);

      await saveTaskFromInteractive(testDir, 'Task content', 'default', {
        issue: 42,
        confirmAtEndMessage: 'Add this issue to tasks?',
      });

      expect(mockConfirm).toHaveBeenNthCalledWith(1, 'Add this issue to tasks?', true);
      expect(mockConfirm).toHaveBeenNthCalledWith(2, 'Auto-create PR?', true);
      const task = loadTasks(testDir).tasks[0]!;
      expect(task.issue).toBe(42);
      expect(task.worktree).toBe(true);
    });
  });

  it('should save base_branch when current branch is not main/master and user confirms', async () => {
    mockGetCurrentBranch.mockReturnValue('feature/custom-base');
    mockConfirm.mockResolvedValueOnce(true);
    mockPromptInput.mockResolvedValueOnce('');
    mockPromptInput.mockResolvedValueOnce('');
    mockConfirm.mockResolvedValueOnce(false);

    await saveTaskFromInteractive(testDir, 'Task content');

    const task = loadTasks(testDir).tasks[0]!;
    expect(task.base_branch).toBe('feature/custom-base');
  });
});
