import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Like } from 'typeorm';
import { Word } from './entities/word.entity';
import { Definition } from './entities/definition.entity';

/**
 * WordsService — business logic for CRUD operations on Word entities.
 * All operations are scoped to a specific userId — users only see their own words.
 */
@Injectable()
export class WordsService {
  constructor(
    @InjectRepository(Word)
    private readonly wordsRepo: Repository<Word>,
    @InjectRepository(Definition)
    private readonly definitionsRepo: Repository<Definition>,
  ) {}

  /** Return words for a user, optionally filtered, with pagination. */
  async findAll(
    userId: string,
    search?: string,
    status?: string,
    page = 1,
    limit = 20,
  ): Promise<{ data: Word[]; total: number; page: number; limit: number }> {
    const where: Record<string, unknown> = { ownerId: userId };
    if (search) where['text'] = Like(`%${search}%`);
    if (status) where['status'] = status;

    const [data, total] = await this.wordsRepo.findAndCount({
      where,
      order: { createdAt: 'DESC' },
      skip: (page - 1) * limit,
      take: limit,
    });

    return { data, total, page, limit };
  }

  /** Return a single word by id, scoped to the requesting user */
  async findOne(id: string, userId: string): Promise<Word> {
    const word = await this.wordsRepo.findOne({
      where: { id, ownerId: userId },
      relations: ['definitions'],
      order: { definitions: { date: 'DESC' } },
    });
    if (!word) {
      throw new NotFoundException(`Word #${id} not found`);
    }
    return word;
  }

  /**
   * Create a new word with an optional first definition.
   * If no definition is provided, the word is marked 'to_be_defined'.
   */
  create(userId: string, text: string, definitionText?: string): Promise<Word> {
    const status = definitionText ? 'active' : 'to_be_defined';
    const word = this.wordsRepo.create({ text, status, ownerId: userId });

    if (definitionText) {
      const definition = new Definition();
      definition.text = definitionText;
      definition.context = null;
      word.definitions = [definition];
    }

    return this.wordsRepo.save(word);
  }

  /**
   * Add a definition to an existing word.
   * Marks the word as 'active' once a definition is provided.
   */
  async addDefinition(
    id: string,
    userId: string,
    text: string,
    context?: string,
  ): Promise<Word> {
    const word = await this.findOne(id, userId);

    const definition = new Definition();
    definition.text = text;
    definition.context = context ?? null;
    definition.word = word;

    word.definitions = [...(word.definitions ?? []), definition];
    word.status = 'active';

    return this.wordsRepo.save(word);
  }

  /** Update the text and/or context of a definition */
  async updateDefinition(
    wordId: string,
    definitionId: string,
    userId: string,
    patch: { text?: string; context?: string | null },
  ): Promise<Definition> {
    const word = await this.findOne(wordId, userId);
    const exists = word.definitions.some((d) => d.id === definitionId);
    if (!exists) {
      throw new NotFoundException(`Definition #${definitionId} not found`);
    }
    const update: Partial<Definition> = {};
    if (patch.text !== undefined) update.text = patch.text;
    if (patch.context !== undefined) update.context = patch.context;
    await this.definitionsRepo.update(definitionId, update);
    return this.definitionsRepo.findOneOrFail({ where: { id: definitionId } });
  }

  /**
   * Remove a definition from a word.
   * If it was the last definition, marks the word back as 'to_be_defined'.
   */
  async removeDefinition(wordId: string, definitionId: string, userId: string): Promise<Word> {
    const word = await this.findOne(wordId, userId);

    const definition = word.definitions.find((d) => d.id === definitionId);
    if (!definition) {
      throw new NotFoundException(`Definition #${definitionId} not found`);
    }

    await this.definitionsRepo.remove(definition);

    const remaining = word.definitions.filter((d) => d.id !== definitionId);
    const status = remaining.length === 0 ? 'to_be_defined' : 'active';
    await this.wordsRepo.update(wordId, { status });

    return this.findOne(wordId, userId);
  }

  /** Delete a word by id, scoped to the requesting user */
  async remove(id: string, userId: string): Promise<void> {
    const word = await this.findOne(id, userId);
    await this.wordsRepo.remove(word);
  }
}
