import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { responseMessages } from '../../messages/response-messages';
import { Countries } from '../../entities/countries.entity';
import { Categories } from '../../entities/categories.entity';
import { CountryRecommendedCategory } from '../../entities/country-recommended-categories.entity';
import { ToggleCountryRecommendedDto } from './dtos/toggle-country-recommended.dto';
import { CountryRecommendedListQueryDto } from './dtos/country-recommended-list-query.dto';
import { UpdateCountryRecommendedOrderDto } from './dtos/update-country-recommended-order.dto';
import { UpdateCountryRecommendedMinApplicationDto } from './dtos/update-country-recommended-min-application.dto';
import { CategoryCampaignListQueryDto } from './dtos/category-campaign-list-query.dto';
import { ToggleCategoryCampaignDto } from './dtos/toggle-category-campaign.dto';
import { ToggleCategoryCampaignHideDto } from './dtos/toggle-category-campaign-hide.dto';
import { UpdateCategoryCampaignOrderDto } from './dtos/update-category-campaign-order.dto';
import { Campaign, CampaignStatus, ProfilePrivacy } from '../../entities/campaign.entity';
import { CampaignCategory } from '../../entities/campaign-caregories.entity';
import { CampaignCategoryCountryOrder } from '../../entities/campaign-category-country-order.entity';
import { CampaignRepository } from '../campaign/campaign.repository';
import { Constants } from '../../common/constants';

@Injectable()
export class CountryRecommendedCategoriesService {
  constructor(
    @InjectRepository(Countries)
    private Countries: Repository<Countries>,

    @InjectRepository(Categories)
    private Categories: Repository<Categories>,

    @InjectRepository(CountryRecommendedCategory)
    private CountryRecommendedCategory: Repository<CountryRecommendedCategory>,

    @InjectRepository(Campaign)
    private Campaign: Repository<Campaign>,

    @InjectRepository(CampaignCategory)
    private CampaignCategory: Repository<CampaignCategory>,

    @InjectRepository(CampaignCategoryCountryOrder)
    private CampaignCategoryCountryOrder: Repository<CampaignCategoryCountryOrder>,

    private campaignRepository: CampaignRepository,
  ) { }

  async toggle(request: ToggleCountryRecommendedDto) {
    const country = await this.Countries.findOne({
      where: { id: request.country_id },
    });
    if (!country) {
      throw new HttpException(responseMessages.en.categories.country_not_exists, HttpStatus.NOT_FOUND);
    }

    const category = await this.Categories.findOne({
      where: { id: request.category_id },
    });
    if (!category) {
      throw new HttpException(responseMessages.en.categories.not_exists, HttpStatus.NOT_FOUND);
    }

    const existing = await this.CountryRecommendedCategory.findOne({
      where: {
        country: { id: request.country_id },
        category: { id: request.category_id },
      },
    });

    if (existing) {
      await this.CountryRecommendedCategory.delete({ id: existing.id });
      return { action: 'removed' };
    }

    const maxOrderResult = await this.CountryRecommendedCategory
      .createQueryBuilder('crc')
      .select('MAX(crc.order_by)', 'max')
      .where('crc.country_id = :countryId', { countryId: request.country_id })
      .getRawOne();

    const record = new CountryRecommendedCategory();
    record.country = country;
    record.category = category;
    record.order_by = Number(maxOrderResult?.max ?? 0) + 1;
    record.setDefaults();

    const created = await this.CountryRecommendedCategory.save(record);
    return { action: 'added', data: created };
  }

  async list(queryParam: CountryRecommendedListQueryDto, authUser: any) {
    const country = await this.Countries.findOne({
      where: { id: queryParam.country_id },
    });
    if (!country) {
      throw new HttpException(responseMessages.en.categories.country_not_exists, HttpStatus.NOT_FOUND);
    }

    const languageCode = authUser?.language || 'en';
    const isRecommended = queryParam.recommended === 'true';

    if (isRecommended) {
      let query = this.CountryRecommendedCategory.createQueryBuilder('crc')
        .innerJoinAndSelect('crc.category', 'categories', 'categories.deleted_at IS NULL')
        .leftJoin(
          'categories.translations',
          'translation_req',
          'translation_req.language_code = :languageCode AND translation_req.deleted_at IS NULL',
          { languageCode },
        )
        .leftJoin(
          'categories.translations',
          'translation_en',
          "translation_en.language_code = 'en' AND translation_en.deleted_at IS NULL",
        )
        .leftJoinAndSelect(
          'categories.translations',
          'translation',
          `(translation.language_code = :languageCode OR (translation.language_code = 'en' AND translation_req.id IS NULL)) AND translation.deleted_at IS NULL`,
          { languageCode },
        )
        .where('crc.country_id = :countryId', { countryId: queryParam.country_id })
        .andWhere('crc.deleted_at IS NULL')
        .andWhere('(translation_req.id IS NOT NULL OR translation_en.id IS NOT NULL)');

      if (authUser?.role !== 'admin') {
        query = query.andWhere('categories.is_active = :is_active', { is_active: true });
      }

      const results = await query
        .orderBy('crc.order_by', 'ASC')
        .addOrderBy('COALESCE(translation_req.title, translation_en.title)', 'ASC')
        .getMany();

      return {
        data: results.map((item) => ({
          recommended_id: item.id,
          order_by: item.order_by,
          min_application: item.min_application,
          ...item.category,
        })),
      };
    }

    let query = this.Categories.createQueryBuilder('categories')
      .leftJoin(
        'categories.translations',
        'translation_req',
        'translation_req.language_code = :languageCode AND translation_req.deleted_at IS NULL',
        { languageCode },
      )
      .leftJoin(
        'categories.translations',
        'translation_en',
        "translation_en.language_code = 'en' AND translation_en.deleted_at IS NULL",
      )
      .leftJoinAndSelect(
        'categories.translations',
        'translation',
        `(translation.language_code = :languageCode OR (translation.language_code = 'en' AND translation_req.id IS NULL)) AND translation.deleted_at IS NULL`,
        { languageCode },
      )
      .where('categories.deleted_at IS NULL')
      .andWhere('(translation_req.id IS NOT NULL OR translation_en.id IS NOT NULL)')
      .andWhere((qb) => {
        const subQuery = qb
          .subQuery()
          .select('crc.category_id')
          .from(CountryRecommendedCategory, 'crc')
          .where('crc.country_id = :countryId', { countryId: queryParam.country_id })
          .andWhere('crc.deleted_at IS NULL')
          .getQuery();
        return `categories.id NOT IN ${subQuery}`;
      })
      .setParameter('countryId', queryParam.country_id);

    query = query.andWhere('categories.is_active = :is_active', { is_active: true });


    const results = await query
      .orderBy('COALESCE(translation_req.title, translation_en.title)', 'ASC')
      .getMany();

    return { data: results };
  }

  async updateOrder(request: UpdateCountryRecommendedOrderDto) {
    const record = await this.CountryRecommendedCategory.findOne({
      where: { id: request.country_recommended_id },
      relations: ['country'],
    });

    if (!record) {
      throw new HttpException(
        responseMessages.en.categories.country_recommended_not_exists,
        HttpStatus.NOT_FOUND,
      );
    }

    const countryId = record.country.id;
    const records = await this.CountryRecommendedCategory.find({
      where: { country: { id: countryId } },
      order: { order_by: 'ASC' },
    });

    const currentIndex = records.findIndex((item) => item.id === record.id);
    const targetIndex = request.order_by - 1;

    if (targetIndex < 0 || targetIndex >= records.length) {
      throw new HttpException(
        responseMessages.en.categories.country_recommended_order_invalid,
        HttpStatus.BAD_REQUEST,
      );
    }

    if (currentIndex === targetIndex) {
      return;
    }

    const [movedRecord] = records.splice(currentIndex, 1);
    records.splice(targetIndex, 0, movedRecord);

    const updates = records.map((item, index) => {
      item.order_by = index + 1;
      return item;
    });

    await this.CountryRecommendedCategory.save(updates);
    return;
  }

  async updateMinApplication(request: UpdateCountryRecommendedMinApplicationDto) {
    const record = await this.CountryRecommendedCategory.findOne({
      where: { id: request.country_recommended_id },
    });

    if (!record) {
      throw new HttpException(
        responseMessages.en.categories.country_recommended_not_exists,
        HttpStatus.NOT_FOUND,
      );
    }

    record.min_application = request.min_application;
    await this.CountryRecommendedCategory.save(record);

    return;
  }

  async campaignList(queryParam: CategoryCampaignListQueryDto) {
    const country = await this.Countries.findOne({
      where: { id: queryParam.country_id },
    });
    if (!country) {
      throw new HttpException(responseMessages.en.categories.country_not_exists, HttpStatus.NOT_FOUND);
    }

    const category = await this.Categories.findOne({
      where: { id: queryParam.category_id },
    });
    if (!category) {
      throw new HttpException(responseMessages.en.categories.not_exists, HttpStatus.NOT_FOUND);
    }

    const page = queryParam.page - 1;
    const pageSize = queryParam.per_page || Constants.categories_list;
    const offset = page * pageSize;
    const nextOffset = queryParam.page * pageSize;

    let query = this.Campaign.createQueryBuilder('campaign')
      .innerJoin(
        'campaign.campaignCategory',
        'campaignCategory',
        'campaignCategory.deleted_at IS NULL',
      )
      .innerJoin(
        'campaignCategory.category',
        'category',
        'category.id = :categoryId AND category.deleted_at IS NULL',
        { categoryId: queryParam.category_id },
      )
      .innerJoin(
        'campaignCategory.countryOrders',
        'countryOrder',
        'countryOrder.country_id = :countryId AND countryOrder.deleted_at IS NULL',
        { countryId: queryParam.country_id },
      )
      .leftJoin('campaign.users', 'users')
      .select([
        'campaign.id',
        'campaign.campaign_name',
        'campaign.platforms',
        'campaign.campaign_image',
        'campaign.attachment',
        'campaign.created_at',
      ])
      .addSelect(['users.company_name', 'users.first_name', 'users.last_name'])
      .addSelect(['campaignCategory.id', 'countryOrder.id', 'countryOrder.order_by', 'countryOrder.is_hidden'])
      .where('campaign.deleted_at IS NULL')
      .andWhere('campaign.status = :status', { status: CampaignStatus.RUNNING })
      .andWhere('campaign.is_active = :is_active', { is_active: true })
      .andWhere('campaign.profile_privacy = :profile_privacy', { profile_privacy: ProfilePrivacy.PUBLIC })
      .orderBy('countryOrder.order_by', 'DESC')
      .addOrderBy('campaign.created_at', 'DESC');

    if (queryParam.search) {
      query = query.andWhere(
        `(campaign.campaign_name ILIKE :search OR users.company_name ILIKE :search OR CONCAT(users.first_name, ' ', users.last_name) ILIKE :search)`,
        { search: `%${queryParam.search}%` },
      );
    }

    const [results, total] = await query.skip(offset).take(pageSize).getManyAndCount();

    return {
      data: results,
      total,
      hasMoreResults: total > nextOffset ? 1 : 0,
    };
  }

  async toggleCampaign(request: ToggleCategoryCampaignDto) {
    const country = await this.Countries.findOne({
      where: { id: request.country_id },
    });
    if (!country) {
      throw new HttpException(responseMessages.en.categories.country_not_exists, HttpStatus.NOT_FOUND);
    }

    const category = await this.Categories.findOne({
      where: { id: request.category_id },
    });
    if (!category) {
      throw new HttpException(responseMessages.en.categories.not_exists, HttpStatus.NOT_FOUND);
    }

    const campaign = await this.Campaign.findOne({
      where: { id: request.campaign_id },
    });
    if (!campaign) {
      throw new HttpException(responseMessages.en.campaign.campaign_not_exists, HttpStatus.NOT_FOUND);
    }

    let campaignCategory = await this.CampaignCategory.findOne({
      where: {
        category: { id: request.category_id },
        campaign: { id: request.campaign_id },
      },
      relations: ['category'],
    });

    const existingCountryOrder = campaignCategory
      ? await this.CampaignCategoryCountryOrder.findOne({
        where: {
          campaignCategory: { id: campaignCategory.id },
          country: { id: request.country_id },
        },
      })
      : null;

    if (existingCountryOrder) {
      await this.CampaignCategoryCountryOrder.delete({ id: existingCountryOrder.id });

      const remainingCount = await this.CampaignCategoryCountryOrder.count({
        where: { campaignCategory: { id: campaignCategory.id } },
      });

      if (remainingCount === 0) {
        await this.CampaignCategory.delete({ id: campaignCategory.id });
      }

      return { action: 'removed' };
    }

    if (!campaignCategory) {
      const record = new CampaignCategory();
      record.category = category;
      record.campaign = campaign;
      record.setDefaults();
      campaignCategory = await this.CampaignCategory.save(record);
      campaignCategory.category = category;
    }

    await this.CampaignCategory.update(campaignCategory.id, {
      is_added_by_admin: true,
    });

    const countryOrder = new CampaignCategoryCountryOrder();
    countryOrder.country = country;
    countryOrder.campaignCategory = campaignCategory;
    countryOrder.order_by = await this.campaignRepository.getNextCountryCategoryOrder(
      request.country_id,
      request.category_id,
    );
    countryOrder.setDefaults();

    await this.CampaignCategoryCountryOrder.save(countryOrder);
    return { action: 'added' };
  }

  async toggleCampaignHide(request: ToggleCategoryCampaignHideDto) {
    const country = await this.Countries.findOne({
      where: { id: request.country_id },
    });
    if (!country) {
      throw new HttpException(responseMessages.en.categories.country_not_exists, HttpStatus.NOT_FOUND);
    }

    const category = await this.Categories.findOne({
      where: { id: request.category_id },
    });
    if (!category) {
      throw new HttpException(responseMessages.en.categories.not_exists, HttpStatus.NOT_FOUND);
    }

    const campaign = await this.Campaign.findOne({
      where: { id: request.campaign_id },
    });
    if (!campaign) {
      throw new HttpException(responseMessages.en.campaign.campaign_not_exists, HttpStatus.NOT_FOUND);
    }

    const campaignCategory = await this.CampaignCategory.findOne({
      where: {
        category: { id: request.category_id },
        campaign: { id: request.campaign_id },
      },
    });

    if (!campaignCategory) {
      throw new HttpException(
        responseMessages.en.categories.campaign_category_not_exists,
        HttpStatus.NOT_FOUND,
      );
    }

    const countryOrder = await this.CampaignCategoryCountryOrder.findOne({
      where: {
        campaignCategory: { id: campaignCategory.id },
        country: { id: request.country_id },
      },
    });

    if (!countryOrder) {
      throw new HttpException(
        responseMessages.en.categories.campaign_category_not_exists,
        HttpStatus.NOT_FOUND,
      );
    }

    countryOrder.is_hidden = !countryOrder.is_hidden;
    await this.CampaignCategoryCountryOrder.save(countryOrder);

    return {
      action: countryOrder.is_hidden ? 'hidden' : 'unhidden',
      is_hidden: countryOrder.is_hidden,
    };
  }

  async updateCampaignOrder(request: UpdateCategoryCampaignOrderDto) {
    const country = await this.Countries.findOne({
      where: { id: request.country_id },
    });
    if (!country) {
      throw new HttpException(responseMessages.en.categories.country_not_exists, HttpStatus.NOT_FOUND);
    }

    const campaignCategory = await this.CampaignCategory.findOne({
      where: { id: request.campaign_category_id },
      relations: ['category'],
    });

    if (!campaignCategory) {
      throw new HttpException(
        responseMessages.en.categories.campaign_category_not_exists,
        HttpStatus.NOT_FOUND,
      );
    }

    const record = await this.CampaignCategoryCountryOrder.findOne({
      where: {
        campaignCategory: { id: request.campaign_category_id },
        country: { id: request.country_id },
      },
    });

    if (!record) {
      throw new HttpException(
        responseMessages.en.categories.campaign_category_not_exists,
        HttpStatus.NOT_FOUND,
      );
    }

    const categoryId = campaignCategory.category.id;
    const records = await this.CampaignCategoryCountryOrder.createQueryBuilder('cco')
      .innerJoinAndSelect('cco.campaignCategory', 'cc', 'cc.deleted_at IS NULL')
      .where('cco.country_id = :countryId', { countryId: request.country_id })
      .andWhere('cc.category_id = :categoryId', { categoryId })
      .andWhere('cco.deleted_at IS NULL')
      .orderBy('cco.order_by', 'ASC')
      .getMany();

    const currentIndex = records.findIndex((item) => item.id === record.id);
    if (currentIndex === -1) {
      throw new HttpException(
        responseMessages.en.categories.campaign_category_order_invalid,
        HttpStatus.BAD_REQUEST,
      );
    }

    // Prefer matching an existing order_by value (UI sends target order), fall back to 1-based position.
    let targetIndex = records.findIndex((item) => item.order_by === request.order_by);
    if (targetIndex === -1) {
      targetIndex = request.order_by - 1;
    }

    if (targetIndex < 0 || targetIndex >= records.length) {
      throw new HttpException(
        responseMessages.en.categories.campaign_category_order_invalid,
        HttpStatus.BAD_REQUEST,
      );
    }

    if (currentIndex === targetIndex) {
      return;
    }

    const [movedRecord] = records.splice(currentIndex, 1);
    records.splice(targetIndex, 0, movedRecord);

    const updates = records.map((item, index) => {
      item.order_by = index + 1;
      return item;
    });

    await this.CampaignCategoryCountryOrder.save(updates);
    return;
  }
}
