import { Injectable } from '@angular/core';
import { Observable, map } from 'rxjs';
import { ApiService } from '../../../services';
import { AuthService } from '@marketplaces/auth';
import { CategoryService } from '../../categories/category.service';
import { ProductDataProvider } from './product-data-provider.interface';
import { Product, ProductCategory, ProductListQuery, ProductListResult, ProductSearchQuery, RelatedProductsQuery } from '../models/product-domain.model';
import { Answer, EngagementListQuery, EngagementListResult, Question, RatingSummary, Review, SubmitQuestionInput, SubmitReviewInput } from '../models/product-engagement.model';

@Injectable({ providedIn: 'root' })
export class ApiProductDataProvider implements ProductDataProvider {
  constructor(
    private readonly apiService: ApiService,
    private readonly categoryService: CategoryService,
    private readonly authService: AuthService
  ) {}

  getProducts(query: ProductListQuery = {}): Observable<ProductListResult> {
    return this.apiService.searchItems('', query.count, query.skip, this.toSearchOptions(query))
      .pipe(map(result => this.toResult(result.items, result.total, query)));
  }

  getProduct(productID: number): Observable<Product> {
    return this.apiService.getItem(productID);
  }

  getCategories(): Observable<ProductCategory[]> {
    return this.categoryService.getAllCategories();
  }

  searchProducts(query: ProductSearchQuery): Observable<ProductListResult> {
    return this.apiService.searchItems(query.search, query.count, query.skip, this.toSearchOptions(query))
      .pipe(map(result => this.toResult(result.items, result.total, query)));
  }

  getFeaturedProducts(query: ProductListQuery = {}): Observable<ProductListResult> {
    return this.apiService.getRandomItems(query.count ?? 10)
      .pipe(map(items => this.toResult(items, items.length, query)));
  }

  getLatestProducts(query: ProductListQuery = {}): Observable<ProductListResult> {
    return this.apiService.searchItems('', query.count, query.skip, { ...this.toSearchOptions(query), sort: 'popular' })
      .pipe(map(result => this.toResult(result.items, result.total, query)));
  }

  getProductsByCategory(categoryID: number, query: ProductListQuery = {}): Observable<ProductListResult> {
    return this.apiService.getCategoryItems(categoryID, query.count, query.skip)
      .pipe(map(items => this.toResult(items, items.length, query)));
  }

  getRelatedProducts(query: RelatedProductsQuery): Observable<ProductListResult> {
    if (query.categoryID == null) {
      return this.getFeaturedProducts({ count: query.count ?? 8 });
    }

    return this.getProductsByCategory(query.categoryID, { count: query.count ?? 8 }).pipe(
      map(result => ({
        ...result,
        items: result.items.filter(item => item.itemID !== query.productID)
      }))
    );
  }

  loadRating(productID: number): Observable<RatingSummary> {
    return this.apiService.getItem(productID).pipe(
      map(item => this.toRatingSummary(this.toReviews(item)))
    );
  }

  loadReviews(productID: number, query: EngagementListQuery = {}): Observable<EngagementListResult<Review>> {
    return this.apiService.getItem(productID).pipe(
      map(item => this.paginate(this.toReviews(item), query))
    );
  }

  loadQuestions(productID: number, query: EngagementListQuery = {}): Observable<EngagementListResult<Question>> {
    return this.apiService.getItem(productID).pipe(
      map(item => this.paginate(this.toQuestions(item), query))
    );
  }

  submitReview(productID: number, input: SubmitReviewInput): Observable<void> {
    return this.apiService.submitReview({
      itemID: productID,
      rating: input.rating,
      comment: input.title.trim() ? `${input.title.trim()}\n\n${input.text.trim()}` : input.text.trim(),
      sessionID: this.authService.session()?.sessionId || 'anonymous',
      timestamp: new Date().toISOString()
    }).pipe(map(() => void 0));
  }

  submitQuestion(productID: number, input: SubmitQuestionInput): Observable<void> {
    return this.apiService.submitQuestion({
      itemID: productID,
      question: input.text.trim(),
      sessionID: this.authService.session()?.sessionId || 'anonymous',
      timestamp: new Date().toISOString()
    }).pipe(map(() => void 0));
  }

  private toSearchOptions(query: ProductListQuery) {
    return {
      categoryIDs: query.categoryIDs,
      minPrice: query.minPrice,
      maxPrice: query.maxPrice,
      tag: query.tag,
      sort: query.sort === 'latest' ? 'popular' : query.sort
    };
  }

  private toResult(items: Product[], total: number, query: ProductListQuery): ProductListResult {
    return {
      items,
      total,
      count: query.count ?? items.length,
      skip: query.skip ?? 0
    };
  }

  private toReviews(product: Product): Review[] {
    const comments = product.comments ?? [];
    const callbacks = product.callbacks ?? [];

    const merged = comments.length > 0
      ? comments.map((comment, index) => ({
          id: comment.id ?? `comment-${product.itemID}-${index}`,
          rating: comment.stars ?? 0,
          content: comment.text ?? '',
          author: comment.author ?? 'Anonymous',
          createdAt: comment.createdAt ?? new Date().toISOString()
        }))
      : callbacks.map((callback, index) => ({
          id: `callback-${product.itemID}-${index}`,
          rating: callback.rating ?? 0,
          content: callback.content ?? '',
          author: callback.userID ?? 'Anonymous',
          createdAt: callback.timestamp ?? new Date().toISOString()
        }));

    return merged
      .filter(review => review.content.trim().length > 0)
      .map(review => {
        const parts = review.content.split('\n\n');
        const maybeTitle = parts.length > 1 ? parts[0].trim() : '';
        const body = parts.length > 1 ? parts.slice(1).join('\n\n').trim() : review.content.trim();

        return {
          id: review.id,
          rating: Math.max(1, Math.min(5, review.rating || 0)),
          title: maybeTitle,
          text: body,
          author: review.author || 'Anonymous',
          anonymous: review.author === 'Anonymous',
          verifiedPurchase: false,
          createdAt: review.createdAt,
          likes: 0,
          dislikes: 0,
          photos: []
        } as Review;
      })
      .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
  }

  private toQuestions(product: Product): Question[] {
    return (product.questions ?? []).map((question, index) => {
      const answer: Answer | null = question.answer
        ? {
            id: `answer-${product.itemID}-${index}`,
            text: question.answer,
            author: 'Seller',
            createdAt: new Date().toISOString(),
            isOfficialSeller: true,
            isAccepted: true,
            likes: 0,
            dislikes: 0
          }
        : null;

      return {
        id: `question-${product.itemID}-${index}`,
        text: question.question,
        author: 'Customer',
        createdAt: new Date().toISOString(),
        likes: question.like ?? question.upvotes ?? 0,
        dislikes: question.dislike ?? question.downvotes ?? 0,
        answers: answer ? [answer] : []
      };
    });
  }

  private toRatingSummary(reviews: Review[]): RatingSummary {
    const baseCounts: Record<number, number> = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };

    for (const review of reviews) {
      const stars = Math.max(1, Math.min(5, Math.round(review.rating)));
      baseCounts[stars] += 1;
    }

    const totalReviews = reviews.length;
    const average = totalReviews > 0
      ? Number((reviews.reduce((acc, review) => acc + review.rating, 0) / totalReviews).toFixed(1))
      : 0;

    return {
      average,
      totalReviews,
      distribution: [5, 4, 3, 2, 1].map(stars => ({
        stars: stars as 1 | 2 | 3 | 4 | 5,
        count: baseCounts[stars],
        share: totalReviews > 0 ? baseCounts[stars] / totalReviews : 0
      }))
    };
  }

  private paginate<T>(items: T[], query: EngagementListQuery): EngagementListResult<T> {
    const pageSize = Math.max(1, query.pageSize ?? 5);
    const page = Math.max(1, query.page ?? 1);
    const skip = (page - 1) * pageSize;

    return {
      items: items.slice(skip, skip + pageSize),
      total: items.length,
      page,
      pageSize
    };
  }
}
