import { Injectable, Logger, Inject, forwardRef } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { HttpService } from '@nestjs/axios';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { lastValueFrom } from 'rxjs';
import { ArticlesService } from './articles.service';
import { LiveBlogService } from './live-blog.service';
import { CreateArticleDto, CreateLiveBlogDto } from './articles.dto';
import {
  ArticleStatus,
  LiveBlogStatus,
  ImagePosition,
  ImageSize,
  VideoPosition,
  Category,
} from './entities';

interface StrapiImage {
  url: string;
  alternativeText?: string;
  caption?: string;
  width?: number;
  height?: number;
  formats?: Record<string, StrapiImageFormat>;
}

interface StrapiImageFormat {
  url: string;
  width: number;
  height: number;
}

interface StrapiArticle {
  id: number;
  documentId: string;
  title: string;
  description: string;
  content: string;
  slug: string;
  publishedAt: string | null;
  createdAt: string;
  updatedAt: string;
  img?: StrapiImage;
  media?: StrapiImage[];
  imagePosition?: string;
  imageSize?: string;
  videoUrl?: string;
  videoPosition?: string;
  videoCaption?: string;
  category?: string;
}

interface StrapiLiveBlog {
  id: number;
  documentId: string;
  title: string;
  description: string;
  slug: string;
  status: 'draft' | 'live' | 'ended' | 'archived';
  publishedAt: string | null;
  createdAt: string;
  updatedAt: string;
  img?: StrapiImage;
  media?: StrapiImage[];
  imagePosition?: string;
  imageSize?: string;
  videoUrl?: string;
  videoPosition?: string;
  videoCaption?: string;
  category?: string;
}

interface StrapiResponse<T> {
  data: T;
  meta: {
    pagination: {
      page: number;
      pageSize: number;
      pageCount: number;
      total: number;
    };
  };
}

@Injectable()
export class StrapiService {
  private readonly logger = new Logger(StrapiService.name);
  private readonly strapiUrl: string;
  private readonly strapiPublicUrl: string;
  private readonly strapiApiToken: string;

  constructor(
    private readonly configService: ConfigService,
    private readonly httpService: HttpService,
    @Inject(forwardRef(() => ArticlesService))
    private readonly articlesService: ArticlesService,
    @Inject(forwardRef(() => LiveBlogService))
    private readonly liveBlogService: LiveBlogService,
    @InjectRepository(Category)
    private readonly categoryRepository: Repository<Category>,
  ) {
    this.strapiUrl =
      this.configService.get<string>('STRAPI_URL') || 'http://localhost:1337';
    this.strapiPublicUrl =
      this.configService.get<string>('STRAPI_PUBLIC_URL') ||
      this.strapiUrl.replace('cms:', 'localhost:');
    this.strapiApiToken =
      this.configService.get<string>('STRAPI_API_TOKEN') || '';
  }

  private getHeaders() {
    // Use public API access - no authentication needed
    // Strapi Public role has been configured to allow article access
    return {};
  }

  private generateSlug(title: string): string {
    // Generate slug from title if missing
    return title
      .toLowerCase()
      .trim()
      .replace(/[^\w\s-]/g, '') // Remove special characters
      .replace(/\s+/g, '-') // Replace spaces with hyphens
      .replace(/-+/g, '-') // Replace multiple hyphens with single hyphen
      .substring(0, 100); // Limit length
  }

  private async findOrCreateCategory(
    categorySlug: string,
  ): Promise<Category | null> {
    if (!categorySlug) {
      return null;
    }

    // Map CMS category slugs to Macedonian display names
    const categoryMap: Record<string, { name: string; description: string }> = {
      general: { name: 'Општо', description: 'Општи вести и теми' },
      sport: { name: 'Спорт', description: 'Спортски вести и анализи' },
      art: { name: 'Уметност', description: 'Уметност, култура и забава' },
      science: { name: 'Наука', description: 'Научни откритија и технологија' },
    };

    const categoryInfo = categoryMap[categorySlug];
    if (!categoryInfo) {
      this.logger.warn(`Unknown category slug: ${categorySlug}`);
      return null;
    }

    // Try to find existing category by slug
    let category = await this.categoryRepository.findOne({
      where: { slug: categorySlug },
    });

    if (!category) {
      // Create new category
      category = this.categoryRepository.create({
        name: categoryInfo.name,
        slug: categorySlug,
        description: categoryInfo.description,
        order: 0,
      });
      category = await this.categoryRepository.save(category);
      this.logger.log(
        `Created new category: ${categorySlug} (${categoryInfo.name})`,
      );
    }

    return category;
  }

  private extractImageUrl(strapiArticle: StrapiArticle): string | undefined {
    // Try to get image from img field first (single image)
    let imageUrl: string | undefined;

    if (strapiArticle.img?.url) {
      imageUrl = strapiArticle.img.url;
    } else if (strapiArticle.media?.[0]?.url) {
      // Try to get first image from media field (multiple images)
      imageUrl = strapiArticle.media[0].url;
    }

    if (!imageUrl) {
      return undefined;
    }

    // If URL is relative, prepend Strapi base URL
    if (imageUrl.startsWith('/')) {
      // Use public URL for frontend access
      return `${this.strapiPublicUrl}${imageUrl}`;
    }

    return imageUrl;
  }

  private extractLiveBlogImageUrl(
    strapiLiveBlog: StrapiLiveBlog,
  ): string | undefined {
    // Try to get image from img field first (single image)
    let imageUrl: string | undefined;

    if (strapiLiveBlog.img?.url) {
      imageUrl = strapiLiveBlog.img.url;
    } else if (strapiLiveBlog.media?.[0]?.url) {
      // Try to get first image from media field (multiple images)
      imageUrl = strapiLiveBlog.media[0].url;
    }

    if (!imageUrl) {
      return undefined;
    }

    // If URL is relative, prepend Strapi base URL
    if (imageUrl.startsWith('/')) {
      // Use public URL for frontend access
      return `${this.strapiPublicUrl}${imageUrl}`;
    }

    return imageUrl;
  }

  async syncArticles(): Promise<void> {
    try {
      this.logger.log('Starting articles sync from Strapi...');

      const response = await lastValueFrom(
        this.httpService.get<StrapiResponse<StrapiArticle[]>>(
          `${this.strapiUrl}/api/articles?populate=*`,
          {
            headers: this.getHeaders(),
          },
        ),
      );

      const strapiArticles = response.data.data;
      let syncedCount = 0;

      for (const strapiArticle of strapiArticles) {
        const imageUrl = this.extractImageUrl(strapiArticle);

        // Find or create category
        let categoryId: string | undefined;
        if (strapiArticle.category) {
          const category = await this.findOrCreateCategory(
            strapiArticle.category,
          );
          if (category) {
            categoryId = category.id;
          }
        }

        // Generate slug if missing or null
        const slug =
          strapiArticle.slug || this.generateSlug(strapiArticle.title);

        const articleData: Partial<CreateArticleDto> = {
          title: strapiArticle.title,
          excerpt: strapiArticle.description || '',
          content: strapiArticle.content,
          slug,
          status: strapiArticle.publishedAt
            ? ArticleStatus.PUBLISHED
            : ArticleStatus.DRAFT,
          tags: [],
          featuredImage: imageUrl,
          imagePosition: (strapiArticle.imagePosition ||
            'top') as ImagePosition,
          imageSize: (strapiArticle.imageSize || 'medium') as ImageSize,
          videoUrl: strapiArticle.videoUrl || '',
          videoPosition: (strapiArticle.videoPosition ||
            'inline') as VideoPosition,
          videoCaption: strapiArticle.videoCaption || '',
          categoryId,
        };

        await this.articlesService.syncFromStrapi(
          strapiArticle.documentId,
          articleData,
        );
        syncedCount++;
      }

      this.logger.log(
        `Successfully synced ${syncedCount} articles from Strapi`,
      );
    } catch (error) {
      this.logger.error('Failed to sync articles from Strapi', error);
      throw error;
    }
  }

  async syncSingleArticle(
    strapiId: string,
    event?:
      | 'entry.create'
      | 'entry.update'
      | 'entry.delete'
      | 'entry.publish'
      | 'entry.unpublish',
  ): Promise<void> {
    try {
      this.logger.log(
        `Syncing single article from Strapi: ${strapiId}, event: ${event}`,
      );

      const response = await lastValueFrom(
        this.httpService.get<StrapiResponse<StrapiArticle>>(
          `${this.strapiUrl}/api/articles/${strapiId}?populate=*`,
          {
            headers: this.getHeaders(),
          },
        ),
      );

      const strapiArticle = response.data.data;

      // Determine status based on publishedAt and event type
      let status: ArticleStatus;
      if (event === 'entry.unpublish') {
        // If it's an unpublish event, always mark as draft
        status = ArticleStatus.DRAFT;
      } else if (strapiArticle.publishedAt) {
        // If publishedAt exists, it's published
        status = ArticleStatus.PUBLISHED;
      } else {
        // Otherwise it's a draft
        status = ArticleStatus.DRAFT;
      }

      const imageUrl = this.extractImageUrl(strapiArticle);

      // Find or create category
      let categoryId: string | undefined;
      if (strapiArticle.category) {
        const category = await this.findOrCreateCategory(
          strapiArticle.category,
        );
        if (category) {
          categoryId = category.id;
        }
      }

      // Generate slug if missing or null
      const slug = strapiArticle.slug || this.generateSlug(strapiArticle.title);

      const articleData: Partial<CreateArticleDto> = {
        title: strapiArticle.title,
        excerpt: strapiArticle.description || '',
        content: strapiArticle.content,
        slug,
        status,
        tags: [],
        featuredImage: imageUrl,
        imagePosition: (strapiArticle.imagePosition || 'top') as ImagePosition,
        imageSize: (strapiArticle.imageSize || 'medium') as ImageSize,
        videoUrl: strapiArticle.videoUrl || '',
        videoPosition: (strapiArticle.videoPosition ||
          'inline') as VideoPosition,
        videoCaption: strapiArticle.videoCaption || '',
        categoryId,
      };

      await this.articlesService.syncFromStrapi(
        strapiArticle.documentId,
        articleData,
      );
      this.logger.log(
        `Successfully synced article: ${strapiArticle.title} with status: ${status}`,
      );
    } catch (error: unknown) {
      // If we get a 404 and it's an unpublish event, we can still mark it as draft
      const axiosError = error as { response?: { status: number } };
      if (event === 'entry.unpublish' && axiosError.response?.status === 404) {
        this.logger.log(
          `Article ${strapiId} not found in Strapi, marking as draft based on unpublish event`,
        );

        // Try to update the article status to draft directly
        try {
          const articleData: Partial<CreateArticleDto> = {
            status: ArticleStatus.DRAFT,
          };

          await this.articlesService.syncFromStrapi(strapiId, articleData);
          this.logger.log(
            `Marked article ${strapiId} as draft based on unpublish event`,
          );
        } catch (syncError) {
          this.logger.error(
            `Failed to mark article ${strapiId} as draft:`,
            syncError,
          );
          throw syncError;
        }
      } else {
        this.logger.error(
          `Failed to sync article ${strapiId} from Strapi`,
          error,
        );
        throw error;
      }
    }
  }

  async syncLiveBlogs(): Promise<void> {
    try {
      this.logger.log('Starting live blogs sync from Strapi...');

      const response = await lastValueFrom(
        this.httpService.get<StrapiResponse<StrapiLiveBlog[]>>(
          `${this.strapiUrl}/api/live-blogs?populate=*`,
          {
            headers: this.getHeaders(),
          },
        ),
      );

      const strapiLiveBlogs = response.data.data;
      let syncedCount = 0;

      for (const strapiLiveBlog of strapiLiveBlogs) {
        const imageUrl = this.extractLiveBlogImageUrl(strapiLiveBlog);

        // Find or create category
        let categoryId: string | undefined;
        if (strapiLiveBlog.category) {
          const category = await this.findOrCreateCategory(
            strapiLiveBlog.category,
          );
          if (category) {
            categoryId = category.id;
          }
        }

        const liveBlogData: Partial<CreateLiveBlogDto> = {
          title: strapiLiveBlog.title,
          description: strapiLiveBlog.description,
          slug: strapiLiveBlog.slug,
          status: this.mapStrapiStatusToLiveBlogStatus(strapiLiveBlog.status),
          featuredImage: imageUrl,
          imagePosition: (strapiLiveBlog.imagePosition ||
            'top') as ImagePosition,
          imageSize: (strapiLiveBlog.imageSize || 'medium') as ImageSize,
          videoUrl: strapiLiveBlog.videoUrl || '',
          videoPosition: (strapiLiveBlog.videoPosition ||
            'inline') as VideoPosition,
          videoCaption: strapiLiveBlog.videoCaption || '',
          categoryId,
        };

        await this.liveBlogService.syncFromStrapi(
          strapiLiveBlog.documentId,
          liveBlogData,
        );
        syncedCount++;
      }

      this.logger.log(
        `Successfully synced ${syncedCount} live blogs from Strapi`,
      );
    } catch (error) {
      this.logger.error('Failed to sync live blogs from Strapi', error);
      throw error;
    }
  }

  async syncSingleLiveBlog(
    strapiId: string,
    event?:
      | 'entry.create'
      | 'entry.update'
      | 'entry.delete'
      | 'entry.publish'
      | 'entry.unpublish',
  ): Promise<void> {
    try {
      this.logger.log(
        `Syncing single live blog from Strapi: ${strapiId}, event: ${event}`,
      );

      const response = await lastValueFrom(
        this.httpService.get<StrapiResponse<StrapiLiveBlog>>(
          `${this.strapiUrl}/api/live-blogs/${strapiId}?populate=*`,
          {
            headers: this.getHeaders(),
          },
        ),
      );

      const strapiLiveBlog = response.data.data;

      // For live blogs, we use the status from Strapi directly
      // but we might want to handle unpublish events differently
      let status = this.mapStrapiStatusToLiveBlogStatus(strapiLiveBlog.status);

      // If it's an unpublish event, set to draft
      if (event === 'entry.unpublish') {
        status = LiveBlogStatus.DRAFT;
      }

      const imageUrl = this.extractLiveBlogImageUrl(strapiLiveBlog);

      // Find or create category
      let categoryId: string | undefined;
      if (strapiLiveBlog.category) {
        const category = await this.findOrCreateCategory(
          strapiLiveBlog.category,
        );
        if (category) {
          categoryId = category.id;
        }
      }

      const liveBlogData: Partial<CreateLiveBlogDto> = {
        title: strapiLiveBlog.title,
        description: strapiLiveBlog.description,
        slug: strapiLiveBlog.slug,
        status,
        featuredImage: imageUrl,
        imagePosition: (strapiLiveBlog.imagePosition || 'top') as ImagePosition,
        imageSize: (strapiLiveBlog.imageSize || 'medium') as ImageSize,
        videoUrl: strapiLiveBlog.videoUrl || '',
        videoPosition: (strapiLiveBlog.videoPosition ||
          'inline') as VideoPosition,
        videoCaption: strapiLiveBlog.videoCaption || '',
        categoryId,
      };

      await this.liveBlogService.syncFromStrapi(
        strapiLiveBlog.documentId,
        liveBlogData,
      );
      this.logger.log(
        `Successfully synced live blog: ${strapiLiveBlog.title} with status: ${status}`,
      );
    } catch (error: unknown) {
      // If we get a 404 and it's an unpublish event, we can still mark it as draft
      const axiosError = error as { response?: { status: number } };
      if (event === 'entry.unpublish' && axiosError.response?.status === 404) {
        this.logger.log(
          `Live blog ${strapiId} not found in Strapi, marking as draft based on unpublish event`,
        );

        // Try to update the live blog status to draft directly
        try {
          const liveBlogData: Partial<CreateLiveBlogDto> = {
            status: LiveBlogStatus.DRAFT,
          };

          await this.liveBlogService.syncFromStrapi(strapiId, liveBlogData);
          this.logger.log(
            `Marked live blog ${strapiId} as draft based on unpublish event`,
          );
        } catch (syncError) {
          this.logger.error(
            `Failed to mark live blog ${strapiId} as draft:`,
            syncError,
          );
          throw syncError;
        }
      } else {
        this.logger.error(
          `Failed to sync live blog ${strapiId} from Strapi`,
          error,
        );
        throw error;
      }
    }
  }

  private mapStrapiStatusToLiveBlogStatus(status: string): LiveBlogStatus {
    switch (status) {
      case 'live':
        return LiveBlogStatus.LIVE;
      case 'draft':
        return LiveBlogStatus.DRAFT;
      case 'ended':
        return LiveBlogStatus.ENDED;
      case 'archived':
        return LiveBlogStatus.ARCHIVED;
      default:
        return LiveBlogStatus.DRAFT;
    }
  }

  async handleWebhook(
    event:
      | 'entry.create'
      | 'entry.update'
      | 'entry.delete'
      | 'entry.publish'
      | 'entry.unpublish',
    data: { documentId: string; model?: string },
  ): Promise<void> {
    this.logger.log(
      `Received webhook event: ${event} for model: ${data.model}`,
    );

    if (event === 'entry.delete') {
      this.logger.log(
        `Handling delete for document: ${data.documentId}, model: ${data.model}`,
      );

      if (data.model === 'article') {
        await this.articlesService.removeByStrapiId(data.documentId);
      } else if (data.model === 'live-blog') {
        await this.liveBlogService.removeByStrapiId(data.documentId);
      } else {
        this.logger.warn(`Cannot delete: unknown model type: ${data.model}`);
      }
      return;
    }

    // Route to appropriate sync method based on model
    if (data.model === 'article') {
      await this.syncSingleArticle(data.documentId, event);
    } else if (data.model === 'live-blog') {
      await this.syncSingleLiveBlog(data.documentId, event);
    } else {
      this.logger.warn(`Unknown model type in webhook: ${data.model}`);
    }
  }

  async updateArticleStatusInStrapi(
    strapiId: string,
    status: ArticleStatus,
  ): Promise<void> {
    try {
      this.logger.log(
        `Updating article ${strapiId} status in Strapi to: ${status}`,
      );

      // Map our status to Strapi status
      let strapiStatus: string;
      switch (status) {
        case ArticleStatus.DRAFT:
          strapiStatus = 'draft';
          break;
        case ArticleStatus.PUBLISHED:
          strapiStatus = 'published';
          break;
        case ArticleStatus.ARCHIVED:
          strapiStatus = 'archived';
          break;
        default:
          strapiStatus = 'draft';
      }

      // For archived status, we need to unpublish first if it's published
      if (status === ArticleStatus.ARCHIVED) {
        await lastValueFrom(
          this.httpService.patch(
            `${this.strapiUrl}/api/articles/${strapiId}/unpublish`,
            {},
            { headers: this.getHeaders() },
          ),
        );
      }

      // Update the status
      await lastValueFrom(
        this.httpService.patch(
          `${this.strapiUrl}/api/articles/${strapiId}`,
          { data: { status: strapiStatus } },
          { headers: this.getHeaders() },
        ),
      );

      this.logger.log(
        `Successfully updated article ${strapiId} status in Strapi to: ${status}`,
      );
    } catch (error) {
      this.logger.error(
        `Failed to update article ${strapiId} status in Strapi:`,
        error,
      );
      throw error;
    }
  }

  async updateLiveBlogStatusInStrapi(
    strapiId: string,
    status: LiveBlogStatus,
  ): Promise<void> {
    try {
      this.logger.log(
        `Updating live blog ${strapiId} status in Strapi to: ${status}`,
      );

      // Map our status to Strapi status
      let strapiStatus: string;
      switch (status) {
        case LiveBlogStatus.DRAFT:
          strapiStatus = 'draft';
          break;
        case LiveBlogStatus.LIVE:
          strapiStatus = 'live';
          break;
        case LiveBlogStatus.ENDED:
          strapiStatus = 'ended';
          break;
        case LiveBlogStatus.ARCHIVED:
          strapiStatus = 'archived';
          break;
        default:
          strapiStatus = 'draft';
      }

      // For archived status, we need to unpublish first if it's published
      if (status === LiveBlogStatus.ARCHIVED) {
        await lastValueFrom(
          this.httpService.patch(
            `${this.strapiUrl}/api/live-blogs/${strapiId}/unpublish`,
            {},
            { headers: this.getHeaders() },
          ),
        );
      }

      // Update the status
      await lastValueFrom(
        this.httpService.patch(
          `${this.strapiUrl}/api/live-blogs/${strapiId}`,
          { data: { status: strapiStatus } },
          { headers: this.getHeaders() },
        ),
      );

      this.logger.log(
        `Successfully updated live blog ${strapiId} status in Strapi to: ${status}`,
      );
    } catch (error) {
      this.logger.error(
        `Failed to update live blog ${strapiId} status in Strapi:`,
        error,
      );
      throw error;
    }
  }

  async deleteArticleFromStrapi(strapiId: string): Promise<void> {
    try {
      this.logger.log(`Deleting article ${strapiId} from Strapi`);

      await lastValueFrom(
        this.httpService.delete(`${this.strapiUrl}/api/articles/${strapiId}`, {
          headers: this.getHeaders(),
        }),
      );

      this.logger.log(`Successfully deleted article ${strapiId} from Strapi`);
    } catch (error) {
      this.logger.error(
        `Failed to delete article ${strapiId} from Strapi:`,
        error,
      );
      throw error;
    }
  }

  async deleteLiveBlogFromStrapi(strapiId: string): Promise<void> {
    try {
      this.logger.log(`Deleting live blog ${strapiId} from Strapi`);

      await lastValueFrom(
        this.httpService.delete(
          `${this.strapiUrl}/api/live-blogs/${strapiId}`,
          { headers: this.getHeaders() },
        ),
      );

      this.logger.log(`Successfully deleted live blog ${strapiId} from Strapi`);
    } catch (error) {
      this.logger.error(
        `Failed to delete live blog ${strapiId} from Strapi:`,
        error,
      );
      throw error;
    }
  }
}
