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

export type GuidelineId = string;
export type TagId = string;
export type Criticality = 'low' | 'medium' | 'high';
export type GuidelineDocumentVersion = string;

export interface GuidelineContent {
  condition: string;
  action: string | null;
  description: string | null;
}

export interface Guideline {
  id: GuidelineId;
  creationUtc: string;
  content: GuidelineContent;
  enabled: boolean;
  tags: TagId[];
  labels: string[];
  metadata: Record<string, unknown>;
  criticality: Criticality;
  title: string | null;
  priority: number;
}

export interface GuidelineDocument {
  id: string;
  version: GuidelineDocumentVersion;
  creation_utc: string;
  condition: string;
  action: string | null;
  description: string | null;
  title: string | null;
  criticality: string;
  enabled: boolean;
  metadata: Record<string, unknown>;
  labels: string[];
  priority: number;
}

export interface GuidelineUpdateParams {
  condition?: string;
  action?: string | null;
  description?: string | null;
  title?: string | null;
  criticality?: Criticality;
  enabled?: boolean;
  priority?: number;
}

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(projectRoot?: string): string {
  const dir = join(ensureRunsDir(projectRoot), '..', 'guidelines');
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
  return join(dir, 'guidelines.json');
}

function readDb(projectRoot?: string): GuidelineDocument[] {
  const path = dbPath(projectRoot);
  try {
    return JSON.parse(readFileSync(path, 'utf-8')) as GuidelineDocument[];
  } catch {
    return [];
  }
}

function writeDb(docs: GuidelineDocument[], projectRoot?: string): void {
  writeFileSync(dbPath(projectRoot), JSON.stringify(docs, null, 2), 'utf-8');
}

function toDocument(g: Guideline): GuidelineDocument {
  return {
    id: g.id,
    version: '0.11.0',
    creation_utc: g.creationUtc,
    condition: g.content.condition,
    action: g.content.action,
    description: g.content.description,
    title: g.title,
    criticality: g.criticality,
    enabled: g.enabled,
    metadata: g.metadata,
    labels: g.labels,
    priority: g.priority,
  };
}

function fromDocument(d: GuidelineDocument): Guideline {
  return {
    id: d.id,
    creationUtc: d.creation_utc,
    content: {
      condition: d.condition,
      action: d.action ?? null,
      description: d.description ?? null,
    },
    title: d.title ?? null,
    criticality: (d.criticality || 'medium') as Criticality,
    enabled: d.enabled ?? true,
    tags: [],
    labels: d.labels ?? [],
    metadata: d.metadata ?? {},
    priority: d.priority ?? 0,
  };
}

export class GuidelineDocumentStore {
  createGuideline(params: {
    condition: string;
    action?: string | null;
    description?: string | null;
    title?: string | null;
    criticality?: Criticality;
    enabled?: boolean;
    labels?: string[];
    priority?: number;
    id?: GuidelineId;
  }, projectRoot?: string): Guideline {
    const docs = readDb(projectRoot);
    const id = params.id || `gl_${generateId()}`;

    if (docs.find(d => d.id === id)) {
      throw new Error(`Guideline with id '${id}' already exists`);
    }

    const guideline: Guideline = {
      id,
      creationUtc: new Date().toISOString(),
      content: {
        condition: params.condition,
        action: params.action ?? null,
        description: params.description ?? null,
      },
      title: params.title ?? null,
      criticality: params.criticality ?? 'medium',
      enabled: params.enabled ?? true,
      tags: [],
      labels: params.labels ?? [],
      metadata: {},
      priority: params.priority ?? 0,
    };

    docs.push(toDocument(guideline));
    writeDb(docs, projectRoot);
    return guideline;
  }

  listGuidelines(params?: {
    tags?: TagId[];
    labels?: string[];
  }, projectRoot?: string): Guideline[] {
    let docs = readDb(projectRoot);

    if (params?.tags && params.tags.length > 0) {
      const tagSet = new Set(params.tags);
      docs = docs.filter(d => d.metadata['tags'] &&
        Array.isArray(d.metadata['tags']) &&
        (d.metadata['tags'] as string[]).some(t => tagSet.has(t)));
    }

    if (params?.labels && params.labels.length > 0) {
      const labelSet = new Set(params.labels);
      docs = docs.filter(d => {
        const gl = fromDocument(d);
        return params.labels!.every(l => gl.labels.includes(l));
      });
    }

    return docs.map(fromDocument);
  }

  readGuideline(id: GuidelineId, projectRoot?: string): Guideline {
    const docs = readDb(projectRoot);
    const doc = docs.find(d => d.id === id);
    if (!doc) throw new Error(`Guideline '${id}' not found`);
    return fromDocument(doc);
  }

  updateGuideline(id: GuidelineId, params: GuidelineUpdateParams, projectRoot?: string): Guideline {
    const docs = readDb(projectRoot);
    const idx = docs.findIndex(d => d.id === id);
    if (idx === -1) throw new Error(`Guideline '${id}' not found`);

    const doc = docs[idx]!;
    if (params.condition !== undefined) doc.condition = params.condition;
    if (params.action !== undefined) doc.action = params.action;
    if (params.description !== undefined) doc.description = params.description;
    if (params.title !== undefined) doc.title = params.title;
    if (params.criticality !== undefined) doc.criticality = params.criticality;
    if (params.enabled !== undefined) doc.enabled = params.enabled;
    if (params.priority !== undefined) doc.priority = params.priority;

    docs[idx] = doc;
    writeDb(docs, projectRoot);
    return fromDocument(doc);
  }

  deleteGuideline(id: GuidelineId, projectRoot?: string): void {
    const docs = readDb(projectRoot);
    const idx = docs.findIndex(d => d.id === id);
    if (idx === -1) throw new Error(`Guideline '${id}' not found`);
    docs.splice(idx, 1);
    writeDb(docs, projectRoot);
  }

  findGuideline(content: GuidelineContent, projectRoot?: string): Guideline {
    const docs = readDb(projectRoot);
    const doc = docs.find(d =>
      d.condition === content.condition &&
      (content.action === undefined || d.action === content.action),
    );
    if (!doc) throw new Error(`Guideline not found for condition='${content.condition}'`);
    return fromDocument(doc);
  }

  upsertLabels(id: GuidelineId, labels: string[], projectRoot?: string): Guideline {
    const docs = readDb(projectRoot);
    const idx = docs.findIndex(d => d.id === id);
    if (idx === -1) throw new Error(`Guideline '${id}' not found`);

    const doc = docs[idx]!;
    const current = new Set(doc.labels || []);
    for (const l of labels) current.add(l);
    doc.labels = [...current];
    writeDb(docs, projectRoot);
    return fromDocument(doc);
  }

  removeLabels(id: GuidelineId, labels: string[], projectRoot?: string): Guideline {
    const docs = readDb(projectRoot);
    const idx = docs.findIndex(d => d.id === id);
    if (idx === -1) throw new Error(`Guideline '${id}' not found`);

    const doc = docs[idx]!;
    const removeSet = new Set(labels);
    doc.labels = (doc.labels || []).filter(l => !removeSet.has(l));
    writeDb(docs, projectRoot);
    return fromDocument(doc);
  }
}
