import fs from 'node:fs';
import path from 'node:path';
import { PrismaClient } from '@prisma/client';

export const DEMO = true;

const raw = fs.readFileSync(path.join(process.cwd(), 'lib', 'demo-data.json'), 'utf8');
const parsed = JSON.parse(raw);

const DATE_FIELDS: Record<string, string[]> = {
  post: ['createdAt', 'publishedAt', 'updatedAt'],
  product: ['createdAt', 'updatedAt'],
  service: ['createdAt', 'updatedAt'],
  project: ['createdAt'],
  testimonial: ['createdAt'],
  quoteRequest: ['createdAt', 'updatedAt'],
  contactMessage: ['createdAt', 'updatedAt'],
  newsletterSubscriber: ['createdAt']
};

function rehydrate(value: any, table: string): any {
  if (Array.isArray(value)) return value.map((v) => rehydrate(v, table));
  if (value && typeof value === 'object') {
    const out: Record<string, any> = {};
    for (const [k, v] of Object.entries(value)) {
      if (v === null) { out[k] = null; continue; }
      if (DATE_FIELDS[table]?.includes(k) && typeof v === 'string') out[k] = new Date(v);
      else out[k] = rehydrate(v, table);
    }
    return out;
  }
  return value;
}

export const dataset: Record<string, any[]> = {};
for (const [table, rows] of Object.entries(parsed as Record<string, any[]>)) {
  dataset[table] = rehydrate(rows, table);
}

const RELS: Record<
  string,
  Record<string, { table: string; sourceFk?: string; targetFk?: string; join?: { table: string; key: string }; single?: boolean }>
> = {
  service: {
    translations: { table: 'serviceTranslation', targetFk: 'serviceId' },
    faqs: { table: 'faq', targetFk: 'serviceId' }
  },
  product: {
    translations: { table: 'productTranslation', targetFk: 'productId' },
    category: { table: 'category', sourceFk: 'categoryId', single: true }
  },
  post: {
    translations: { table: 'postTranslation', targetFk: 'postId' },
    author: { table: 'author', sourceFk: 'authorId', single: true },
    tags: { table: 'tag', join: { table: 'postTag', key: 'tagId' } }
  },
  project: { translations: { table: 'projectTranslation', targetFk: 'projectId' } },
  category: { translations: { table: 'categoryTranslation', targetFk: 'categoryId' } },
  testimonial: { translations: { table: 'testimonialTranslation', targetFk: 'testimonialId' } },
  author: { translations: { table: 'authorTranslation', targetFk: 'authorId' } },
  tag: { translations: { table: 'tagTranslation', targetFk: 'tagId' } },
  faq: { translations: { table: 'faqTranslation', targetFk: 'faqId' } }
};

function eq(a: any, b: any): boolean {
  if (a instanceof Date && typeof b === 'string') return a.toISOString() === b || new Date(b).getTime() === a.getTime();
  return a === b;
}

function fieldMatch(v: any, cond: any): boolean {
  if (cond && typeof cond === 'object' && !(cond instanceof Date) && Array.isArray(cond) === false) {
    for (const key of Object.keys(cond)) {
      switch (key) {
        case 'equals':
          if (!eq(v, cond.equals)) return false;
          break;
        case 'not':
          if (Array.isArray(cond.not)) { if (cond.not.includes(v)) return false; }
          else if (eq(v, cond.not)) return false;
          break;
        case 'in':
          if (!cond.in.includes(v)) return false;
          break;
        case 'notIn':
          if (cond.notIn.includes(v)) return false;
          break;
        case 'contains': {
          const str = String(v ?? '').toLowerCase();
          if (!str.includes(String(cond.contains).toLowerCase())) return false;
          break;
        }
        case 'startsWith':
          if (!String(v ?? '').startsWith(cond.startsWith)) return false;
          break;
        case 'endsWith':
          if (!String(v ?? '').endsWith(cond.endsWith)) return false;
          break;
        case 'lt':
          if (!(v < cond.lt)) return false;
          break;
        case 'lte':
          if (!(v <= cond.lte)) return false;
          break;
        case 'gt':
          if (!(v > cond.gt)) return false;
          break;
        case 'gte':
          if (!(v >= cond.gte)) return false;
          break;
        case 'mode':
          break;
        default:
          break;
      }
    }
    return true;
  }
  return eq(v, cond);
}

function getRelated(tableKey: string, row: any, relName: string, rel: any): any[] {
  if (rel.join) {
    const links = dataset[rel.join.table].filter((l) => l.postId === row.id);
    const ids = links.map((l) => l[rel.join.key]);
    return dataset[rel.table].filter((t) => ids.includes(t.id));
  }
  if (rel.sourceFk) {
    const id = row[rel.sourceFk];
    return id == null ? [] : dataset[rel.table].filter((t) => t.id === id);
  }
  if (rel.targetFk) return dataset[rel.table].filter((r) => r[rel.targetFk] === row.id);
  return [];
}

function matches(tableKey: string, row: any, where: any): boolean {
  if (!where) return true;
  for (const key of Object.keys(where)) {
    const cond = where[key];
    if (key === 'AND') { if (!(cond as any[]).every((w) => matches(tableKey, row, w))) return false; continue; }
    if (key === 'OR') { if (!(cond as any[]).some((w) => matches(tableKey, row, w))) return false; continue; }
    if (key === 'NOT') { if (matches(tableKey, row, cond)) return false; continue; }

    const rel = RELS[tableKey]?.[key];
    if (rel) {
      const related = getRelated(tableKey, row, key, rel);
      if (cond && typeof cond === 'object') {
        if ('some' in cond) { if (!related.some((r) => matches(rel.table, r, cond.some))) return false; }
        if ('every' in cond) { if (!related.every((r) => matches(rel.table, r, cond.every))) return false; }
        if ('none' in cond) { if (related.some((r) => matches(rel.table, r, cond.none))) return false; }
      } else {
        if (!related.some((r) => matches(rel.table, r, cond))) return false;
      }
      continue;
    }
    if (!fieldMatch(row[key], cond)) return false;
  }
  return true;
}

function countRelated(tableKey: string, row: any, relName: string): number {
  const rel = RELS[tableKey]?.[relName];
  if (rel) {
    const j = rel.join;
    if (j) return dataset[j.table].filter((l) => l[j.key] === row.id).length;
    const fk = rel.sourceFk;
    if (fk) {
      const id = row[fk];
      return id == null ? 0 : dataset[rel.table].filter((t) => t.id === id).length;
    }
    const tfk = rel.targetFk;
    if (tfk) return dataset[rel.table].filter((r) => r[tfk] === row.id).length;
    return 0;
  }
  let count = 0;
  for (const [owner, rels] of Object.entries(RELS)) {
    for (const r of Object.values(rels)) {
      if (r.table !== tableKey) continue;
      const rj = r.join;
      if (rj) {
        count += dataset[rj.table].filter((l) => l[rj.key] === row.id).length;
      } else {
        const sfk = r.sourceFk;
        const tfk = r.targetFk;
        if (sfk) count += dataset[owner].filter((o) => o[sfk] === row.id).length;
        else if (tfk) count += dataset[owner].filter((o) => o[tfk] === row.id).length;
      }
    }
  }
  return count;
}

function hydrate(row: any, tableKey: string, include: any): any {
  if (!include) return row;
  for (const relName of Object.keys(include)) {
    if (relName === '_count' && include[relName]?.select) {
      row._count = {};
      for (const k of Object.keys(include[relName].select)) row._count[k] = countRelated(tableKey, row, k);
      continue;
    }
    const rel = RELS[tableKey]?.[relName];
    if (!rel) continue;
    const args = include[relName];
    let relRows = getRelated(tableKey, row, relName, rel);
    if (args && typeof args === 'object' && args.where) relRows = relRows.filter((r) => matches(rel.table, r, args.where));
    relRows = relRows.map((r) => hydrate({ ...r }, rel.table, args?.include));
    row[relName] = rel.single ? relRows[0] ?? null : relRows;
  }
  return row;
}

function sortRows(rows: any[], orderBy: any): any[] {
  if (!orderBy) return rows;
  const list = Array.isArray(orderBy) ? orderBy : [orderBy];
  return rows
    .map((r, i) => [r, i] as [any, number])
    .sort(([a, ai], [b, bi]) => {
      for (const o of list) {
        const k = Object.keys(o)[0];
        const dir = o[k] === 'desc' ? -1 : 1;
        const va = a[k];
        const vb = b[k];
        if (va == null && vb == null) continue;
        if (va == null) return -1 * dir;
        if (vb == null) return 1 * dir;
        let d: number;
        if (va instanceof Date || vb instanceof Date) {
          const ta = new Date(va).getTime();
          const tb = new Date(vb).getTime();
          d = ta < tb ? -1 : ta > tb ? 1 : 0;
        } else {
          d = va < vb ? -1 : va > vb ? 1 : 0;
        }
        if (d !== 0) return d * dir;
      }
      return ai - bi;
    })
    .map(([r]) => r);
}

function pick(row: any, select: any): any {
  if (!select) return row;
  const out: Record<string, any> = {};
  for (const k of Object.keys(select)) {
    const v = select[k];
    if (v === true || (v && typeof v === 'object')) out[k] = row[k];
  }
  return out;
}

function buildModel(table: string) {
  return {
    async findMany(args: any = {}): Promise<any[]> {
      let rows = dataset[table] ?? [];
      if (args.where) rows = rows.filter((r) => matches(table, r, args.where));
      rows = sortRows(rows, args.orderBy);
      if (args.skip) rows = rows.slice(args.skip);
      if (args.take !== undefined) rows = rows.slice(0, args.take);
      if (args.include) rows = rows.map((r) => hydrate({ ...r }, table, args.include));
      if (args.select) rows = rows.map((r) => pick(r, args.select));
      return rows;
    },
    async findUnique(args: any = {}) {
      let row = (dataset[table] ?? []).find((r) => matches(table, r, args.where));
      if (!row) return null;
      const out = args.include ? hydrate({ ...row }, table, args.include) : { ...row };
      return args.select ? pick(out, args.select) : out;
    },
    async findFirst(args: any = {}) {
      let rows = dataset[table] ?? [];
      if (args.where) rows = rows.filter((r) => matches(table, r, args.where));
      rows = sortRows(rows, args.orderBy);
      const row = rows[0];
      if (!row) return null;
      const out = args.include ? hydrate({ ...row }, table, args.include) : { ...row };
      return args.select ? pick(out, args.select) : out;
    },
    async findFirstOrThrow(args: any = {}) {
      const row = await this.findFirst(args);
      if (!row) throw new Error('No record found');
      return row;
    },
    async count(args: any = {}) {
      let rows = dataset[table] ?? [];
      if (args.where) rows = rows.filter((r) => matches(table, r, args.where));
      return rows.length;
    },
    async create(args: any = {}) {
      return { id: 'demo-' + Math.random().toString(36).slice(2), ...(args.data ?? {}) };
    },
    async createMany(args: any = {}) {
      return { count: Array.isArray(args.data) ? args.data.length : 1 };
    },
    async update(args: any = {}) {
      return { id: args.where?.id ?? 'demo', ...(args.data ?? {}) };
    },
    async updateMany(args: any = {}) {
      return { count: 1 };
    },
    async upsert(args: any = {}) {
      return { id: args.where?.id ?? 'demo', ...(args.update ?? args.create ?? {}) };
    },
    async delete(args: any = {}) {
      return { id: args.where?.id ?? 'demo' };
    },
    async deleteMany() {
      return { count: 0 };
    }
  };
}

const modelNames = [
  'adminUser', 'siteSetting', 'service', 'serviceTranslation', 'category', 'categoryTranslation',
  'product', 'productTranslation', 'author', 'authorTranslation', 'tag', 'tagTranslation',
  'post', 'postTranslation', 'project', 'projectTranslation', 'testimonial', 'testimonialTranslation',
  'faq', 'faqTranslation', 'quoteRequest', 'contactMessage', 'newsletterSubscriber', 'postTag'
];

const prisma = {} as unknown as PrismaClient;

for (const name of modelNames) {
  (prisma as any)[name] = buildModel(name);
}

(prisma as any).$transaction = async (arg: any) =>
  Array.isArray(arg) ? Promise.all(arg) : arg();

export { prisma };
export default prisma;