import { pool, query } from '../db/connection.js';
import { ulid } from 'ulid';

async function syncTags(client: any, recipeId: string, tags: string[]) {
  await client.query('DELETE FROM recipe_tags WHERE recipe_id = $1', [recipeId]);
  for (const tagName of tags) {
    const trimmed = tagName.trim();
    if (!trimmed) continue;
    const slug = trimmed.toLowerCase().replace(/ä/g,'ae').replace(/ö/g,'oe').replace(/ü/g,'ue').replace(/ß/g,'ss').replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'');
    const { rows } = await client.query('SELECT id FROM tags WHERE slug = $1', [slug]);
    let tagId: string;
    if (rows.length === 0) {
      tagId = ulid();
      await client.query('INSERT INTO tags (id, name, slug) VALUES ($1, $2, $3)', [tagId, trimmed, slug]);
    } else {
      tagId = rows[0].id;
    }
    await client.query('INSERT INTO recipe_tags (recipe_id, tag_id) VALUES ($1, $2) ON CONFLICT DO NOTHING', [recipeId, tagId]);
  }
}

function slugify(text: string): string {
  return text
    .toLowerCase()
    .replace(/ä/g, 'ae').replace(/ö/g, 'oe').replace(/ü/g, 'ue').replace(/ß/g, 'ss')
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '');
}

async function ensureUniqueSlug(baseSlug: string, excludeId?: string): Promise<string> {
  let slug = baseSlug;
  let i = 1;
  while (true) {
    const { rows } = excludeId
      ? await query('SELECT id FROM recipes WHERE slug = $1 AND id != $2', [slug, excludeId])
      : await query('SELECT id FROM recipes WHERE slug = $1', [slug]);
    if (rows.length === 0) return slug;
    slug = `${baseSlug}-${i++}`;
  }
}

export interface CreateRecipeInput {
  title: string;
  description?: string;
  category_id?: string;
  difficulty?: 'easy' | 'medium' | 'hard';
  prep_time?: number;
  cook_time?: number;
  servings?: number;
  image_url?: string;
  source_url?: string;
  ingredients?: { amount?: number; unit?: string; name: string; group_name?: string; sort_order?: number }[];
  steps?: { step_number: number; instruction: string; duration_minutes?: number }[];
  tags?: string[];
}

function mapTimeFields(row: any) {
  if (!row) return row;
  row.prep_time_min = row.prep_time ?? null;
  row.cook_time_min = row.cook_time ?? null;
  row.total_time_min = row.total_time ?? null;
  return row;
}

export async function listRecipes(opts: {
  page?: number; limit?: number; category_id?: string; category_slug?: string;
  favorite?: boolean; difficulty?: string; maxTime?: number; userId?: string;
}) {
  const page = opts.page || 1;
  const limit = opts.limit || 20;
  const offset = (page - 1) * limit;
  const conditions: string[] = [];
  const params: any[] = [];
  let paramIdx = 1;

  let joins = 'LEFT JOIN categories c ON r.category_id = c.id';
  if (opts.userId) {
    joins += ` LEFT JOIN user_favorites uf ON r.id = uf.recipe_id AND uf.user_id = $${paramIdx}`;
    params.push(opts.userId);
    paramIdx++;
  }

  if (opts.category_id) { conditions.push(`r.category_id = $${paramIdx}`); params.push(opts.category_id); paramIdx++; }
  if (opts.category_slug) { conditions.push(`c.slug = $${paramIdx}`); params.push(opts.category_slug); paramIdx++; }
  if (opts.favorite !== undefined && opts.userId) {
    conditions.push('uf.user_id IS ' + (opts.favorite ? 'NOT NULL' : 'NULL'));
  }
  if (opts.difficulty) { conditions.push(`r.difficulty = $${paramIdx}`); params.push(opts.difficulty); paramIdx++; }
  if (opts.maxTime) { conditions.push(`r.total_time <= $${paramIdx}`); params.push(opts.maxTime); paramIdx++; }

  const where = conditions.length ? 'WHERE ' + conditions.join(' AND ') : '';

  const countResult = await query(`SELECT COUNT(*) as total FROM recipes r ${joins} ${where}`, params);
  const total = parseInt(countResult.rows[0].total);

  const dataParams = [...params, limit, offset];
  const selectFav = opts.userId ? '(uf.user_id IS NOT NULL) as is_favorite' : 'r.is_favorite';
  const dataResult = await query(
    `SELECT r.*, c.name as category_name, c.slug as category_slug, ${selectFav}
     FROM recipes r ${joins} ${where} ORDER BY r.created_at DESC LIMIT $${paramIdx} OFFSET $${paramIdx + 1}`,
    dataParams
  );

  const data = dataResult.rows.map(mapTimeFields);
  return { data, total, page, limit, totalPages: Math.ceil(total / limit) };
}

export async function getRecipeBySlug(slug: string) {
  const { rows } = await query(
    'SELECT r.*, c.name as category_name FROM recipes r LEFT JOIN categories c ON r.category_id = c.id WHERE r.slug = $1',
    [slug]
  );
  if (rows.length === 0) return null;
  const recipe = rows[0];

  const ingResult = await query('SELECT * FROM ingredients WHERE recipe_id = $1 ORDER BY sort_order', [recipe.id]);
  recipe.ingredients = ingResult.rows;

  const stepResult = await query('SELECT * FROM steps WHERE recipe_id = $1 ORDER BY step_number', [recipe.id]);
  recipe.steps = stepResult.rows;

  const noteResult = await query('SELECT * FROM notes WHERE recipe_id = $1 ORDER BY created_at DESC', [recipe.id]);
  recipe.notes = noteResult.rows;

  const tagResult = await query(
    'SELECT t.name FROM tags t JOIN recipe_tags rt ON t.id = rt.tag_id WHERE rt.recipe_id = $1',
    [recipe.id]
  );
  recipe.tags = tagResult.rows.map((t: any) => t.name);
  mapTimeFields(recipe);
  return recipe;
}

export async function createRecipe(input: CreateRecipeInput) {
  const id = ulid();
  const slug = await ensureUniqueSlug(slugify(input.title));
  const totalTime = (input.prep_time || 0) + (input.cook_time || 0) || null;

  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    await client.query(`
      INSERT INTO recipes (id, title, slug, description, category_id, difficulty, prep_time, cook_time, total_time, servings, image_url, source_url)
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
    `, [id, input.title, slug, input.description || null, input.category_id || null,
        input.difficulty || 'medium', input.prep_time || null, input.cook_time || null, totalTime,
        input.servings || 4, input.image_url || null, input.source_url || null]);

    if (input.ingredients) {
      for (let i = 0; i < input.ingredients.length; i++) {
        const ing = input.ingredients[i];
        await client.query(
          'INSERT INTO ingredients (id, recipe_id, amount, unit, name, group_name, sort_order) VALUES ($1, $2, $3, $4, $5, $6, $7)',
          [ulid(), id, ing.amount || null, ing.unit || null, ing.name, ing.group_name || null, ing.sort_order ?? i]
        );
      }
    }

    if (input.steps) {
      for (const step of input.steps) {
        await client.query(
          'INSERT INTO steps (id, recipe_id, step_number, instruction, duration_minutes) VALUES ($1, $2, $3, $4, $5)',
          [ulid(), id, step.step_number, step.instruction, step.duration_minutes || null]
        );
      }
    }

    if (input.tags && input.tags.length > 0) {
      await syncTags(client, id, input.tags);
    }

    await client.query('COMMIT');
  } catch (e) {
    await client.query('ROLLBACK');
    throw e;
  } finally {
    client.release();
  }

  return getRecipeBySlug(slug);
}

export async function updateRecipe(id: string, input: Partial<CreateRecipeInput>) {
  const { rows } = await query('SELECT * FROM recipes WHERE id = $1', [id]);
  if (rows.length === 0) return null;
  const existing = rows[0];

  const slug = input.title ? await ensureUniqueSlug(slugify(input.title), id) : existing.slug;
  const totalTime = ((input.prep_time ?? existing.prep_time) || 0) + ((input.cook_time ?? existing.cook_time) || 0) || null;

  await query(`
    UPDATE recipes SET title=$1, slug=$2, description=$3, category_id=$4, difficulty=$5, prep_time=$6, cook_time=$7, total_time=$8, servings=$9, image_url=$10, source_url=$11, updated_at=NOW()
    WHERE id=$12
  `, [
    input.title ?? existing.title, slug, input.description ?? existing.description,
    input.category_id ?? existing.category_id, input.difficulty ?? existing.difficulty,
    input.prep_time ?? existing.prep_time, input.cook_time ?? existing.cook_time, totalTime,
    input.servings ?? existing.servings, input.image_url ?? existing.image_url,
    input.source_url ?? existing.source_url, id
  ]);

  if (input.ingredients) {
    await query('DELETE FROM ingredients WHERE recipe_id = $1', [id]);
    for (let i = 0; i < input.ingredients.length; i++) {
      const ing = input.ingredients[i];
      await query(
        'INSERT INTO ingredients (id, recipe_id, amount, unit, name, group_name, sort_order) VALUES ($1, $2, $3, $4, $5, $6, $7)',
        [ulid(), id, ing.amount || null, ing.unit || null, ing.name, ing.group_name || null, ing.sort_order ?? i]
      );
    }
  }

  if (input.steps) {
    await query('DELETE FROM steps WHERE recipe_id = $1', [id]);
    for (const step of input.steps) {
      await query(
        'INSERT INTO steps (id, recipe_id, step_number, instruction, duration_minutes) VALUES ($1, $2, $3, $4, $5)',
        [ulid(), id, step.step_number, step.instruction, step.duration_minutes || null]
      );
    }
  }

  if (input.tags) {
    const client = await pool.connect();
    try {
      await syncTags(client, id, input.tags);
    } finally {
      client.release();
    }
  }

  return getRecipeBySlug(slug);
}

export async function deleteRecipe(id: string): Promise<boolean> {
  const result = await query('DELETE FROM recipes WHERE id = $1', [id]);
  return (result.rowCount ?? 0) > 0;
}

export async function toggleFavorite(id: string, userId?: string) {
  const { rows } = await query('SELECT id, is_favorite FROM recipes WHERE id = $1', [id]);
  if (rows.length === 0) return null;

  if (!userId) {
    const newVal = rows[0].is_favorite ? 0 : 1;
    await query('UPDATE recipes SET is_favorite = $1 WHERE id = $2', [newVal, id]);
    return { id, is_favorite: newVal };
  }

  const { rows: favRows } = await query(
    'SELECT id FROM user_favorites WHERE user_id = $1 AND recipe_id = $2', [userId, id]
  );

  if (favRows.length > 0) {
    await query('DELETE FROM user_favorites WHERE user_id = $1 AND recipe_id = $2', [userId, id]);
    return { id, is_favorite: false };
  } else {
    await query('INSERT INTO user_favorites (id, user_id, recipe_id) VALUES ($1, $2, $3)', [ulid(), userId, id]);
    return { id, is_favorite: true };
  }
}

export async function getRandomRecipe(categorySlugs?: string[]) {
  let sql = 'SELECT r.slug FROM recipes r';
  const params: string[] = [];
  
  if (categorySlugs && categorySlugs.length > 0) {
    sql += ' JOIN categories c ON r.category_id = c.id WHERE c.slug = ANY($1)';
    params.push(categorySlugs as any);
  }
  
  sql += ' ORDER BY RANDOM() LIMIT 1';
  
  const { rows } = await query(sql, params);
  if (rows.length === 0) return null;
  return getRecipeBySlug(rows[0].slug);
}

export async function searchRecipes(q: string) {
  const likePattern = `%${q}%`;
  const { rows } = await query(`
    SELECT r.*, c.name as category_name,
      similarity(r.title, $1) + similarity(COALESCE(r.description,''), $1) as rank
    FROM recipes r
    LEFT JOIN categories c ON r.category_id = c.id
    WHERE r.title ILIKE $2 OR r.description ILIKE $2
    ORDER BY rank DESC
  `, [q, likePattern]);
  return rows.map(mapTimeFields);
}

export async function listCategories() {
  const { rows } = await query('SELECT * FROM categories ORDER BY sort_order');
  return rows;
}

export async function createCategory(name: string) {
  const id = ulid();
  const slug = name.toLowerCase().replace(/ä/g,'ae').replace(/ö/g,'oe').replace(/ü/g,'ue').replace(/ß/g,'ss').replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'');
  const { rows } = await query('SELECT MAX(sort_order) as m FROM categories');
  const maxOrder = rows[0].m || 0;
  await query('INSERT INTO categories (id, name, slug, sort_order) VALUES ($1, $2, $3, $4)', [id, name, slug, maxOrder + 1]);
  return { id, name, slug, sort_order: maxOrder + 1 };
}
