import { Injectable, NotFoundException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { In, Repository } from "typeorm";
import { UpdateFaqDto } from "./dto/update-faq.dto";
import { UpsertFaqDto } from "./dto/upsert-faq.dto";
import { FaqItem } from "./entities/faq-item.entity";

export interface FaqResponse {
  id: string;
  question: string;
  answer: string;
  pages: string[];
  createdAt: Date;
  updatedAt: Date;
}

// FaqItem.pages is stored as a JSON string (or legacy plain string, or
// null) — this always comes back as a clean array, so every caller (admin
// list/create/update, build-time resolution) gets the same shape
// regardless of how the row was originally written.
function parsePages(raw: string | null): string[] {
  if (!raw) return [];
  try {
    const parsed = JSON.parse(raw);
    if (Array.isArray(parsed)) return parsed.filter((p): p is string => typeof p === "string");
  } catch {
    // Legacy rows (from before multi-page tagging) stored a single plain
    // string, not JSON — fall through and treat it as a 1-item tag.
  }
  return [raw];
}

function toResponse(faq: FaqItem): FaqResponse {
  return {
    id: faq.id,
    question: faq.question,
    answer: faq.answer,
    pages: parsePages(faq.pages),
    createdAt: faq.createdAt,
    updatedAt: faq.updatedAt,
  };
}

@Injectable()
export class FaqService {
  constructor(@InjectRepository(FaqItem) private readonly repo: Repository<FaqItem>) {}

  async list(search?: string): Promise<FaqResponse[]> {
    const qb = this.repo.createQueryBuilder("faq").orderBy("faq.createdAt", "DESC");
    const q = search?.trim();
    if (q) qb.where("faq.question LIKE :q OR faq.answer LIKE :q OR faq.pages LIKE :q", { q: `%${q}%` });
    const rows = await qb.getMany();
    return rows.map(toResponse);
  }

  /**
   * Resolves a page's ordered list of FAQ ids (see "faq-selection" content
   * blocks) back into full {question, answer} objects, in the same order —
   * used at build time by the frontend (see resolveFaqSelection in
   * frontend/src/lib/content-fetch.ts). Ids that no longer exist (an FAQ
   * was deleted from the library after a page selected it) are silently
   * dropped rather than erroring.
   */
  async findByIds(ids: string[]): Promise<FaqResponse[]> {
    if (ids.length === 0) return [];
    const found = await this.repo.find({ where: { id: In(ids) } });
    const byId = new Map(found.map((f) => [f.id, f]));
    return ids.map((id) => byId.get(id)).filter((f): f is FaqItem => !!f).map(toResponse);
  }

  async create(dto: UpsertFaqDto): Promise<FaqResponse> {
    const faq = await this.repo.save(
      this.repo.create({
        question: dto.question,
        answer: dto.answer,
        pages: dto.pages && dto.pages.length > 0 ? JSON.stringify(dto.pages) : null,
      }),
    );
    return toResponse(faq);
  }

  async update(id: string, dto: UpdateFaqDto): Promise<FaqResponse> {
    const faq = await this.repo.findOne({ where: { id } });
    if (!faq) throw new NotFoundException(`No FAQ with id "${id}"`);
    if (dto.question !== undefined) faq.question = dto.question;
    if (dto.answer !== undefined) faq.answer = dto.answer;
    if (dto.pages !== undefined) faq.pages = dto.pages && dto.pages.length > 0 ? JSON.stringify(dto.pages) : null;
    return toResponse(await this.repo.save(faq));
  }

  async remove(id: string): Promise<void> {
    const faq = await this.repo.findOne({ where: { id } });
    if (!faq) throw new NotFoundException(`No FAQ with id "${id}"`);
    await this.repo.remove(faq);
  }
}
