import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { ensureRunsDir } from './runs-dir.js';
import type { GuidelineId } from './guideline-store.js';

export type JourneyId = string;
export type JourneyNodeId = string;
export type JourneyEdgeId = string;

export interface JourneyNode {
  id: JourneyNodeId;
  creationUtc: string;
  action: string | null;
  tools: string[];
  metadata: Record<string, unknown>;
  description: string | null;
  labels: string[];
}

export interface JourneyEdge {
  id: JourneyEdgeId;
  creationUtc: string;
  source: JourneyNodeId;
  target: JourneyNodeId;
  condition: string | null;
  metadata: Record<string, unknown>;
}

export interface Journey {
  id: JourneyId;
  creationUtc: string;
  description: string;
  triggers: GuidelineId[];
  title: string;
  rootId: JourneyNodeId;
  tags: string[];
  labels: string[];
  priority: number;
}

interface JourneyDocument {
  id: string;
  version: string;
  creation_utc: string;
  title: string;
  description: string;
  root_id: JourneyNodeId;
  labels: string[];
  priority: number;
}

interface NodeDocument {
  id: string;
  node_id: JourneyNodeId;
  journey_id: JourneyId;
  creation_utc: string;
  action: string | null;
  tools: string[];
  metadata: Record<string, unknown>;
  description: string | null;
  labels: string[];
}

interface EdgeDocument {
  id: string;
  journey_id: JourneyId;
  creation_utc: string;
  source: JourneyNodeId;
  target: JourneyNodeId;
  condition: string | null;
  metadata: Record<string, unknown>;
}

interface TriggerDocument {
  id: string;
  journey_id: JourneyId;
  trigger: GuidelineId;
  creation_utc: string;
}

function generateId(): string {
  const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  let result = '';
  for (let i = 0; i < 10; i++) {
    result += chars[Math.floor(Math.random() * chars.length)];
  }
  return result;
}

function dbPath(name: string, projectRoot?: string): string {
  const dir = join(ensureRunsDir(projectRoot), '..', 'journeys');
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
  return join(dir, `${name}.json`);
}

function readCollection<T>(name: string, projectRoot?: string): T[] {
  try {
    return JSON.parse(readFileSync(dbPath(name, projectRoot), 'utf-8')) as T[];
  } catch {
    return [];
  }
}

function writeCollection<T>(name: string, data: T[], projectRoot?: string): void {
  writeFileSync(dbPath(name, projectRoot), JSON.stringify(data, null, 2), 'utf-8');
}

export class JourneyStore {
  createJourney(params: {
    title: string;
    description: string;
    triggers?: GuidelineId[];
    labels?: string[];
    priority?: number;
  }, projectRoot?: string): Journey {
    const id = `jny_${generateId()}`;
    const rootId = `node_${generateId()}`;
    const creationUtc = new Date().toISOString();

    const journey: Journey = {
      id,
      creationUtc,
      description: params.description,
      triggers: params.triggers || [],
      title: params.title,
      rootId,
      tags: [],
      labels: params.labels || [],
      priority: params.priority || 0,
    };

    const journeys = readCollection<JourneyDocument>('journeys', projectRoot);
    journeys.push({
      id,
      version: '0.7.0',
      creation_utc: creationUtc,
      title: params.title,
      description: params.description,
      root_id: rootId,
      labels: params.labels || [],
      priority: params.priority || 0,
    });
    writeCollection('journeys', journeys, projectRoot);

    const root: JourneyNode = {
      id: rootId,
      creationUtc,
      action: null,
      tools: [],
      metadata: {},
      description: null,
      labels: [],
    };
    const nodes = readCollection<NodeDocument>('nodes', projectRoot);
    nodes.push({
      id: `nd_${generateId()}`,
      node_id: rootId,
      journey_id: id,
      creation_utc: creationUtc,
      action: null,
      tools: [],
      metadata: {},
      description: null,
      labels: [],
    });
    writeCollection('nodes', nodes, projectRoot);

    return journey;
  }

  readJourney(id: JourneyId, projectRoot?: string): Journey {
    const journeys = readCollection<JourneyDocument>('journeys', projectRoot);
    const doc = journeys.find(j => j.id === id);
    if (!doc) throw new Error(`Journey '${id}' not found`);

    const triggers = readCollection<TriggerDocument>('triggers', projectRoot)
      .filter(t => t.journey_id === id)
      .map(t => t.trigger);

    return {
      id: doc.id,
      creationUtc: doc.creation_utc,
      description: doc.description,
      triggers,
      title: doc.title,
      rootId: doc.root_id,
      tags: [],
      labels: doc.labels || [],
      priority: doc.priority || 0,
    };
  }

  deleteJourney(id: JourneyId, projectRoot?: string): void {
    let journeys = readCollection<JourneyDocument>('journeys', projectRoot);
    const idx = journeys.findIndex(j => j.id === id);
    if (idx === -1) throw new Error(`Journey '${id}' not found`);
    journeys.splice(idx, 1);
    writeCollection('journeys', journeys, projectRoot);

    let nodes = readCollection<NodeDocument>('nodes', projectRoot);
    nodes = nodes.filter(n => n.journey_id !== id);
    writeCollection('nodes', nodes, projectRoot);

    let edges = readCollection<EdgeDocument>('edges', projectRoot);
    edges = edges.filter(e => e.journey_id !== id);
    writeCollection('edges', edges, projectRoot);

    let triggers = readCollection<TriggerDocument>('triggers', projectRoot);
    triggers = triggers.filter(t => t.journey_id !== id);
    writeCollection('triggers', triggers, projectRoot);
  }

  listJourneys(projectRoot?: string): Journey[] {
    const journeys = readCollection<JourneyDocument>('journeys', projectRoot);
    return journeys.map(j => this.readJourney(j.id, projectRoot));
  }

  createNode(journeyId: JourneyId, params: {
    action?: string | null;
    tools?: string[];
    description?: string | null;
    labels?: string[];
    id?: JourneyNodeId;
  }, projectRoot?: string): JourneyNode {
    const nodeId = params.id || `node_${generateId()}`;
    const creationUtc = new Date().toISOString();

    const node: JourneyNode = {
      id: nodeId,
      creationUtc,
      action: params.action ?? null,
      tools: params.tools || [],
      metadata: {},
      description: params.description ?? null,
      labels: params.labels || [],
    };

    const nodes = readCollection<NodeDocument>('nodes', projectRoot);
    nodes.push({
      id: `nd_${generateId()}`,
      node_id: nodeId,
      journey_id: journeyId,
      creation_utc: creationUtc,
      action: node.action,
      tools: node.tools,
      metadata: node.metadata,
      description: node.description,
      labels: node.labels,
    });
    writeCollection('nodes', nodes, projectRoot);

    return node;
  }

  listNodes(journeyId: JourneyId, projectRoot?: string): JourneyNode[] {
    const docs = readCollection<NodeDocument>('nodes', projectRoot)
      .filter(n => n.journey_id === journeyId);

    const nodes = docs.map(d => ({
      id: d.node_id,
      creationUtc: d.creation_utc,
      action: d.action,
      tools: d.tools,
      metadata: d.metadata,
      description: d.description,
      labels: d.labels || [],
    }));

    nodes.push({
      id: 'end' as JourneyNodeId,
      creationUtc: new Date().toISOString(),
      action: null,
      tools: [],
      metadata: {},
      description: null,
      labels: [],
    });

    return nodes;
  }

  createEdge(journeyId: JourneyId, params: {
    source: JourneyNodeId;
    target: JourneyNodeId;
    condition?: string | null;
  }, projectRoot?: string): JourneyEdge {
    const creationUtc = new Date().toISOString();
    const edge: JourneyEdge = {
      id: `edge_${generateId()}`,
      creationUtc,
      source: params.source,
      target: params.target,
      condition: params.condition ?? null,
      metadata: {},
    };

    const edges = readCollection<EdgeDocument>('edges', projectRoot);
    edges.push({
      id: edge.id,
      journey_id: journeyId,
      creation_utc: creationUtc,
      source: params.source,
      target: params.target,
      condition: params.condition ?? null,
      metadata: {},
    });
    writeCollection('edges', edges, projectRoot);

    return edge;
  }

  listEdges(journeyId: JourneyId, nodeId?: JourneyNodeId, projectRoot?: string): JourneyEdge[] {
    let docs = readCollection<EdgeDocument>('edges', projectRoot)
      .filter(e => e.journey_id === journeyId);

    if (nodeId) {
      docs = docs.filter(e => e.source === nodeId || e.target === nodeId);
    }

    return docs.map(d => ({
      id: d.id,
      creationUtc: d.creation_utc,
      source: d.source,
      target: d.target,
      condition: d.condition,
      metadata: d.metadata,
    }));
  }

  deleteEdge(edgeId: JourneyEdgeId, projectRoot?: string): void {
    let edges = readCollection<EdgeDocument>('edges', projectRoot);
    const idx = edges.findIndex(e => e.id === edgeId);
    if (idx === -1) throw new Error(`Edge '${edgeId}' not found`);
    edges.splice(idx, 1);
    writeCollection('edges', edges, projectRoot);
  }

  addTrigger(journeyId: JourneyId, trigger: GuidelineId, projectRoot?: string): boolean {
    const triggers = readCollection<TriggerDocument>('triggers', projectRoot);
    if (triggers.find(t => t.journey_id === journeyId && t.trigger === trigger)) {
      return false;
    }
    triggers.push({
      id: `trg_${generateId()}`,
      journey_id: journeyId,
      trigger,
      creation_utc: new Date().toISOString(),
    });
    writeCollection('triggers', triggers, projectRoot);
    return true;
  }

  removeTrigger(journeyId: JourneyId, trigger: GuidelineId, projectRoot?: string): boolean {
    let triggers = readCollection<TriggerDocument>('triggers', projectRoot);
    const len = triggers.length;
    triggers = triggers.filter(t => !(t.journey_id === journeyId && t.trigger === trigger));
    writeCollection('triggers', triggers, projectRoot);
    return triggers.length < len;
  }
}
