import { DatabaseAdapter, createDatabaseAdapter } from '../../src/database/database-adapter';
import { NodeRepository } from '../../src/database/node-repository';
import { TemplateRepository } from '../../src/templates/template-repository';
import { ParsedNode } from '../../src/parsers/node-parser';
import { TemplateWorkflow, TemplateNode, TemplateUser, TemplateDetail } from '../../src/templates/template-fetcher';
import * as fs from 'fs';
import * as path from 'path';
import { vi } from 'vitest';

/**
 * Database test utilities for n8n-mcp
 * Provides helpers for creating, seeding, and managing test databases
 */

export interface TestDatabaseOptions {
  /**
   * Use in-memory database (default: true)
   * When false, creates a temporary file database
   */
  inMemory?: boolean;
  
  /**
   * Custom database path (only used when inMemory is false)
   */
  dbPath?: string;
  
  /**
   * Initialize with schema (default: true)
   */
  initSchema?: boolean;
  
  /**
   * Enable FTS5 support if available (default: false)
   */
  enableFTS5?: boolean;
}

export interface TestDatabase {
  adapter: DatabaseAdapter;
  nodeRepository: NodeRepository;
  templateRepository: TemplateRepository;
  path: string;
  cleanup: () => Promise<void>;
}

export interface DatabaseSnapshot {
  nodes: any[];
  templates: any[];
  metadata: {
    createdAt: string;
    nodeCount: number;
    templateCount: number;
  };
}

/**
 * Creates a test database with repositories
 */
export async function createTestDatabase(options: TestDatabaseOptions = {}): Promise<TestDatabase> {
  const {
    inMemory = true,
    dbPath,
    initSchema = true,
    enableFTS5 = false
  } = options;
  
  // Determine database path
  const finalPath = inMemory 
    ? ':memory:' 
    : dbPath || path.join(__dirname, `../temp/test-${Date.now()}.db`);
  
  // Ensure directory exists for file-based databases
  if (!inMemory) {
    const dir = path.dirname(finalPath);
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }
  }
  
  // Create database adapter
  const adapter = await createDatabaseAdapter(finalPath);
  
  // Initialize schema if requested
  if (initSchema) {
    await initializeDatabaseSchema(adapter, enableFTS5);
  }
  
  // Create repositories
  const nodeRepository = new NodeRepository(adapter);
  const templateRepository = new TemplateRepository(adapter);
  
  // Cleanup function
  const cleanup = async () => {
    adapter.close();
    if (!inMemory && fs.existsSync(finalPath)) {
      fs.unlinkSync(finalPath);
    }
  };
  
  return {
    adapter,
    nodeRepository,
    templateRepository,
    path: finalPath,
    cleanup
  };
}

/**
 * Initializes database schema from SQL file
 */
export async function initializeDatabaseSchema(adapter: DatabaseAdapter, enableFTS5 = false): Promise<void> {
  const schemaPath = path.join(__dirname, '../../src/database/schema.sql');
  const schema = fs.readFileSync(schemaPath, 'utf-8');
  
  // Execute main schema
  adapter.exec(schema);
  
  // Optionally initialize FTS5 tables
  if (enableFTS5 && adapter.checkFTS5Support()) {
    adapter.exec(`
      CREATE VIRTUAL TABLE IF NOT EXISTS templates_fts USING fts5(
        name, 
        description,
        content='templates',
        content_rowid='id'
      );
      
      -- Trigger to keep FTS index in sync
      CREATE TRIGGER IF NOT EXISTS templates_ai AFTER INSERT ON templates BEGIN
        INSERT INTO templates_fts(rowid, name, description) 
        VALUES (new.id, new.name, new.description);
      END;
      
      CREATE TRIGGER IF NOT EXISTS templates_au AFTER UPDATE ON templates BEGIN
        UPDATE templates_fts 
        SET name = new.name, description = new.description 
        WHERE rowid = new.id;
      END;
      
      CREATE TRIGGER IF NOT EXISTS templates_ad AFTER DELETE ON templates BEGIN
        DELETE FROM templates_fts WHERE rowid = old.id;
      END;
    `);
  }
}

/**
 * Seeds test nodes into the database
 */
export async function seedTestNodes(
  nodeRepository: NodeRepository, 
  nodes: Partial<ParsedNode>[] = []
): Promise<ParsedNode[]> {
  const defaultNodes: ParsedNode[] = [
    createTestNode({
      nodeType: 'nodes-base.httpRequest',
      displayName: 'HTTP Request',
      description: 'Makes HTTP requests',
      category: 'Core Nodes',
      isAITool: true
    }),
    createTestNode({
      nodeType: 'nodes-base.webhook',
      displayName: 'Webhook',
      description: 'Receives webhook calls',
      category: 'Core Nodes',
      isTrigger: true,
      isWebhook: true
    }),
    createTestNode({
      nodeType: 'nodes-base.slack',
      displayName: 'Slack',
      description: 'Send messages to Slack',
      category: 'Communication',
      isAITool: true
    })
  ];
  
  const allNodes = [...defaultNodes, ...nodes.map(n => createTestNode(n))];
  
  for (const node of allNodes) {
    nodeRepository.saveNode(node);
  }
  
  return allNodes;
}

/**
 * Seeds test templates into the database
 */
export async function seedTestTemplates(
  templateRepository: TemplateRepository,
  templates: Partial<TemplateWorkflow>[] = []
): Promise<TemplateWorkflow[]> {
  const defaultTemplates: TemplateWorkflow[] = [
    createTestTemplate({
      id: 1,
      name: 'Simple HTTP Workflow',
      description: 'Basic HTTP request workflow',
      nodes: [{ id: 1, name: 'HTTP Request', icon: 'http' }]
    }),
    createTestTemplate({
      id: 2,
      name: 'Webhook to Slack',
      description: 'Webhook that sends to Slack',
      nodes: [
        { id: 1, name: 'Webhook', icon: 'webhook' },
        { id: 2, name: 'Slack', icon: 'slack' }
      ]
    })
  ];
  
  const allTemplates = [...defaultTemplates, ...templates.map(t => createTestTemplate(t))];
  
  for (const template of allTemplates) {
    // Convert to TemplateDetail format for saving
    const detail: TemplateDetail = {
      id: template.id,
      name: template.name,
      description: template.description,
      views: template.totalViews,
      createdAt: template.createdAt,
      workflow: { 
        nodes: template.nodes?.map((n, i) => ({
          id: `node_${i}`,
          name: n.name,
          type: `n8n-nodes-base.${n.name.toLowerCase()}`,
          position: [250 + i * 200, 300],
          parameters: {}
        })) || [],
        connections: {},
        settings: {}
      }
    };
    await templateRepository.saveTemplate(template, detail);
  }
  
  return allTemplates;
}

/**
 * Creates a test node with defaults
 */
export function createTestNode(overrides: Partial<ParsedNode> = {}): ParsedNode {
  return {
    style: 'programmatic',
    nodeType: 'nodes-base.test',
    displayName: 'Test Node',
    description: 'A test node',
    category: 'Test',
    properties: [],
    credentials: [],
    isAITool: false,
    isTrigger: false,
    isWebhook: false,
    operations: [],
    version: '1',
    isVersioned: false,
    packageName: 'n8n-nodes-base',
    documentation: undefined,
    ...overrides
  };
}

/**
 * Creates a test template with defaults
 */
export function createTestTemplate(overrides: Partial<TemplateWorkflow> = {}): TemplateWorkflow {
  const id = overrides.id || Math.floor(Math.random() * 10000);
  return {
    id,
    name: `Test Template ${id}`,
    description: 'A test template',
    nodes: overrides.nodes || [],
    user: overrides.user || {
      id: 1,
      name: 'Test User',
      username: 'testuser',
      verified: false
    },
    createdAt: overrides.createdAt || new Date().toISOString(),
    totalViews: overrides.totalViews || 100,
    ...overrides
  };
}

/**
 * Resets database to clean state
 */
export async function resetDatabase(adapter: DatabaseAdapter): Promise<void> {
  // Drop all tables
  adapter.exec(`
    DROP TABLE IF EXISTS templates_fts;
    DROP TABLE IF EXISTS templates;
    DROP TABLE IF EXISTS nodes;
  `);
  
  // Reinitialize schema
  await initializeDatabaseSchema(adapter);
}

/**
 * Creates a database snapshot
 */
export async function createDatabaseSnapshot(adapter: DatabaseAdapter): Promise<DatabaseSnapshot> {
  const nodes = adapter.prepare('SELECT * FROM nodes').all();
  const templates = adapter.prepare('SELECT * FROM templates').all();
  
  return {
    nodes,
    templates,
    metadata: {
      createdAt: new Date().toISOString(),
      nodeCount: nodes.length,
      templateCount: templates.length
    }
  };
}

/**
 * Restores database from snapshot
 */
export async function restoreDatabaseSnapshot(
  adapter: DatabaseAdapter, 
  snapshot: DatabaseSnapshot
): Promise<void> {
  // Reset database first
  await resetDatabase(adapter);
  
  // Restore nodes
  const nodeStmt = adapter.prepare(`
    INSERT INTO nodes (
      node_type, package_name, display_name, description,
      category, development_style, is_ai_tool, is_trigger,
      is_webhook, is_versioned, version, documentation,
      properties_schema, operations, credentials_required
    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `);
  
  for (const node of snapshot.nodes) {
    nodeStmt.run(
      node.node_type,
      node.package_name,
      node.display_name,
      node.description,
      node.category,
      node.development_style,
      node.is_ai_tool,
      node.is_trigger,
      node.is_webhook,
      node.is_versioned,
      node.version,
      node.documentation,
      node.properties_schema,
      node.operations,
      node.credentials_required
    );
  }
  
  // Restore templates
  const templateStmt = adapter.prepare(`
    INSERT INTO templates (
      id, workflow_id, name, description,
      author_name, author_username, author_verified,
      nodes_used, workflow_json, categories,
      views, created_at, updated_at, url
    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `);
  
  for (const template of snapshot.templates) {
    templateStmt.run(
      template.id,
      template.workflow_id,
      template.name,
      template.description,
      template.author_name,
      template.author_username,
      template.author_verified,
      template.nodes_used,
      template.workflow_json,
      template.categories,
      template.views,
      template.created_at,
      template.updated_at,
      template.url
    );
  }
}

/**
 * Loads JSON fixtures into database
 */
export async function loadFixtures(
  adapter: DatabaseAdapter,
  fixturePath: string
): Promise<void> {
  const fixtures = JSON.parse(fs.readFileSync(fixturePath, 'utf-8'));
  
  if (fixtures.nodes) {
    const nodeRepo = new NodeRepository(adapter);
    for (const node of fixtures.nodes) {
      nodeRepo.saveNode(node);
    }
  }
  
  if (fixtures.templates) {
    const templateRepo = new TemplateRepository(adapter);
    for (const template of fixtures.templates) {
      // Convert to proper format
      const detail: TemplateDetail = {
        id: template.id,
        name: template.name,
        description: template.description,
        views: template.views || template.totalViews || 0,
        createdAt: template.createdAt,
        workflow: template.workflow || {
          nodes: template.nodes?.map((n: any, i: number) => ({
            id: `node_${i}`,
            name: n.name,
            type: `n8n-nodes-base.${n.name.toLowerCase()}`,
            position: [250 + i * 200, 300],
            parameters: {}
          })) || [],
          connections: {},
          settings: {}
        }
      };
      await templateRepo.saveTemplate(template, detail);
    }
  }
}

/**
 * Database test helpers for common operations
 */
export const dbHelpers = {
  /**
   * Counts rows in a table
   */
  countRows(adapter: DatabaseAdapter, table: string): number {
    const result = adapter.prepare(`SELECT COUNT(*) as count FROM ${table}`).get() as { count: number };
    return result.count;
  },
  
  /**
   * Checks if a node exists
   */
  nodeExists(adapter: DatabaseAdapter, nodeType: string): boolean {
    const result = adapter.prepare('SELECT 1 FROM nodes WHERE node_type = ?').get(nodeType);
    return !!result;
  },
  
  /**
   * Gets all node types
   */
  getAllNodeTypes(adapter: DatabaseAdapter): string[] {
    const rows = adapter.prepare('SELECT node_type FROM nodes').all() as { node_type: string }[];
    return rows.map(r => r.node_type);
  },
  
  /**
   * Clears a specific table
   */
  clearTable(adapter: DatabaseAdapter, table: string): void {
    adapter.exec(`DELETE FROM ${table}`);
  },
  
  /**
   * Executes raw SQL
   */
  executeSql(adapter: DatabaseAdapter, sql: string): void {
    adapter.exec(sql);
  }
};

/**
 * Creates a mock database adapter for unit tests
 */
export function createMockDatabaseAdapter(): DatabaseAdapter {
  const mockDb = {
    prepare: vi.fn(),
    exec: vi.fn(),
    close: vi.fn(),
    pragma: vi.fn(),
    inTransaction: false,
    transaction: vi.fn((fn) => fn()),
    checkFTS5Support: vi.fn(() => false)
  };
  
  return mockDb as unknown as DatabaseAdapter;
}

/**
 * Transaction test helper
 * Note: better-sqlite3 transactions are synchronous
 */
export async function withTransaction<T>(
  adapter: DatabaseAdapter,
  fn: () => Promise<T>
): Promise<T | null> {
  try {
    adapter.exec('BEGIN');
    const result = await fn();
    // Always rollback for testing
    adapter.exec('ROLLBACK');
    return null; // Indicate rollback happened
  } catch (error) {
    adapter.exec('ROLLBACK');
    throw error;
  }
}

/**
 * Performance test helper
 */
export async function measureDatabaseOperation(
  name: string,
  operation: () => Promise<void>
): Promise<number> {
  const start = performance.now();
  await operation();
  const duration = performance.now() - start;
  console.log(`[DB Performance] ${name}: ${duration.toFixed(2)}ms`);
  return duration;
}