import {
  Injectable,
  Inject,
  NotFoundException,
  ConflictException,
  Logger,
  OnApplicationBootstrap,
} from '@nestjs/common';
import { v4 as uuidv4 } from 'uuid';
import { BlogPost, BlogPostStatus } from '@domain/entities/blog-post.entity';
import {
  BlogPostRepository,
  BlogPostFilters,
  BLOG_POST_REPOSITORY,
} from '@domain/ports/out/blog-post.repository';
import { CreateBlogPostDto, UpdateBlogPostDto } from '../dto/blog-post.dto';
import { StoragePort, STORAGE_PORT } from '@domain/ports/out/storage.port';

const BLOG_IMAGES_BUCKET = 'xpeditis-blog';

@Injectable()
export class BlogService implements OnApplicationBootstrap {
  private readonly logger = new Logger(BlogService.name);

  constructor(
    @Inject(BLOG_POST_REPOSITORY)
    private readonly blogPostRepository: BlogPostRepository,
    @Inject(STORAGE_PORT)
    private readonly storage: StoragePort
  ) {}

  async onApplicationBootstrap(): Promise<void> {
    try {
      await this.storage.ensureBucket(BLOG_IMAGES_BUCKET);
      this.logger.log(`Blog images bucket "${BLOG_IMAGES_BUCKET}" is ready`);
    } catch (err: any) {
      this.logger.warn(`Could not ensure blog images bucket: ${err?.message}`);
    }
  }

  async createPost(dto: CreateBlogPostDto): Promise<BlogPost> {
    const slugTaken = await this.blogPostRepository.slugExists(dto.slug);
    if (slugTaken) {
      throw new ConflictException(`Slug "${dto.slug}" is already in use`);
    }

    let post = BlogPost.create({
      id: uuidv4(),
      title: dto.title,
      slug: dto.slug,
      excerpt: dto.excerpt,
      content: dto.content,
      coverImageUrl: dto.coverImageUrl,
      category: dto.category,
      tags: dto.tags ?? [],
      authorName: dto.authorName,
      metaTitle: dto.metaTitle,
      metaDescription: dto.metaDescription,
      primaryKeyword: dto.primaryKeyword,
      secondaryKeywords: dto.secondaryKeywords ?? [],
      aiSummary: dto.aiSummary,
      faq: dto.faq ?? [],
      keyTakeaways: dto.keyTakeaways ?? [],
      aiEntities: dto.aiEntities ?? [],
    });

    if (dto.scheduledAt) {
      const schedDate = new Date(dto.scheduledAt);
      post = schedDate > new Date() ? post.schedule(schedDate) : post.publish();
    }

    return this.blogPostRepository.save(post);
  }

  async updatePost(id: string, dto: UpdateBlogPostDto): Promise<BlogPost> {
    const post = await this.findOrFail(id);

    if (dto.slug && dto.slug !== post.slug) {
      const slugTaken = await this.blogPostRepository.slugExists(dto.slug, id);
      if (slugTaken) {
        throw new ConflictException(`Slug "${dto.slug}" is already in use`);
      }
    }

    let updated = post.update({
      title: dto.title,
      slug: dto.slug,
      excerpt: dto.excerpt,
      content: dto.content,
      coverImageUrl: dto.coverImageUrl,
      category: dto.category,
      tags: dto.tags,
      authorName: dto.authorName,
      isFeatured: dto.isFeatured,
      metaTitle: dto.metaTitle,
      metaDescription: dto.metaDescription,
      primaryKeyword: dto.primaryKeyword,
      secondaryKeywords: dto.secondaryKeywords,
      aiSummary: dto.aiSummary,
      faq: dto.faq,
      keyTakeaways: dto.keyTakeaways,
      aiEntities: dto.aiEntities,
    });

    if (dto.scheduledAt) {
      const schedDate = new Date(dto.scheduledAt);
      updated = schedDate > new Date() ? updated.schedule(schedDate) : updated.publish();
    } else if (dto.status !== undefined && dto.status !== post.status) {
      if (dto.status === BlogPostStatus.PUBLISHED) updated = updated.publish();
      else if (dto.status === BlogPostStatus.ARCHIVED) updated = updated.archive();
      else if (dto.status === BlogPostStatus.DRAFT) updated = updated.unpublish();
      else if (dto.status === BlogPostStatus.SCHEDULED && post.publishedAt)
        updated = updated.schedule(post.publishedAt);
    }

    return this.blogPostRepository.save(updated);
  }

  /**
   * Soft-delete a post: move it to the trash. It can be restored later or
   * permanently deleted from the trash.
   */
  async deletePost(id: string): Promise<void> {
    const post = await this.findOrFail(id);
    await this.blogPostRepository.save(post.trash());
  }

  /** Restore a post from the trash. */
  async restorePost(id: string): Promise<BlogPost> {
    const post = await this.findOrFail(id);
    return this.blogPostRepository.save(post.restore());
  }

  /**
   * Permanently delete a post (hard delete). Also removes its cover image from
   * storage when it is a locally hosted blog image (best-effort).
   */
  async permanentlyDeletePost(id: string): Promise<void> {
    const post = await this.findOrFail(id);

    const coverUrl = post.coverImageUrl;
    if (coverUrl) {
      const match = coverUrl.match(/\/api\/v1\/blog\/images\/([^/?#]+)$/);
      if (match) {
        try {
          await this.storage.delete({ bucket: BLOG_IMAGES_BUCKET, key: `blog-images/${match[1]}` });
        } catch (err: any) {
          this.logger.warn(`Could not delete cover image for post ${id}: ${err?.message}`);
        }
      }
    }

    await this.blogPostRepository.delete(id);
  }

  /**
   * Duplicate a post as a new draft with a unique slug.
   */
  async duplicatePost(id: string): Promise<BlogPost> {
    const source = await this.findOrFail(id);
    const slug = await this.generateUniqueSlug(source.slug);

    const copy = BlogPost.create({
      id: uuidv4(),
      title: `${source.title} (copie)`,
      slug,
      excerpt: source.excerpt,
      content: source.content,
      coverImageUrl: source.coverImageUrl,
      category: source.category,
      tags: [...source.tags],
      authorName: source.authorName,
      metaTitle: source.metaTitle,
      metaDescription: source.metaDescription,
      primaryKeyword: source.primaryKeyword,
      secondaryKeywords: [...source.secondaryKeywords],
      aiSummary: source.aiSummary,
      faq: source.faq.map(item => ({ ...item })),
      keyTakeaways: [...source.keyTakeaways],
      aiEntities: [...source.aiEntities],
    });

    return this.blogPostRepository.save(copy);
  }

  private async generateUniqueSlug(baseSlug: string): Promise<string> {
    let candidate = `${baseSlug}-copie`;
    let counter = 2;
    while (await this.blogPostRepository.slugExists(candidate)) {
      candidate = `${baseSlug}-copie-${counter}`;
      counter += 1;
    }
    return candidate;
  }

  async getPostById(id: string): Promise<BlogPost> {
    return this.findOrFail(id);
  }

  async getPublishedPostBySlug(slug: string): Promise<BlogPost> {
    const post = await this.blogPostRepository.findBySlug(slug);
    if (!post || !post.isVisibleToPublic()) {
      throw new NotFoundException('Article not found');
    }
    return post;
  }

  async listPublishedPosts(
    filters: BlogPostFilters
  ): Promise<{ posts: BlogPost[]; total: number }> {
    const publishedFilters: BlogPostFilters = {
      ...filters,
      status: BlogPostStatus.PUBLISHED,
      includeScheduled: true,
    };
    const [posts, total] = await Promise.all([
      this.blogPostRepository.findByFilters(publishedFilters),
      this.blogPostRepository.count(publishedFilters),
    ]);
    return { posts, total };
  }

  async listAllPosts(filters: BlogPostFilters): Promise<{ posts: BlogPost[]; total: number }> {
    const [posts, total] = await Promise.all([
      this.blogPostRepository.findByFilters(filters),
      this.blogPostRepository.count(filters),
    ]);
    return { posts, total };
  }

  private async findOrFail(id: string): Promise<BlogPost> {
    const post = await this.blogPostRepository.findById(id);
    if (!post) {
      throw new NotFoundException(`Blog post with id "${id}" not found`);
    }
    return post;
  }
}
