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`);
    }

    const 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,
    });

    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,
    });

    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();
    }

    return this.blogPostRepository.save(updated);
  }

  async deletePost(id: string): Promise<void> {
    await this.findOrFail(id);
    await this.blogPostRepository.delete(id);
  }

  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.isPublished()) {
      throw new NotFoundException('Article not found');
    }
    return post;
  }

  async listPublishedPosts(
    filters: BlogPostFilters
  ): Promise<{ posts: BlogPost[]; total: number }> {
    const publishedFilters: BlogPostFilters = {
      ...filters,
      status: BlogPostStatus.PUBLISHED,
    };
    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;
  }
}
