import {
  Injectable,
  BadRequestException,
  NotFoundException,
  InternalServerErrorException,
} from '@nestjs/common';
import { Kysely } from 'kysely';
import { DB } from '@/database/database.type';
import { Logger } from 'pino-nestjs';
import { QrService } from '@/utils/qr/qr.service';
import { FlyerService } from '@/utils/flyer/flyer.service';
import { ImageService } from '@/utils/image/image.service';
import { ConfigService } from '@nestjs/config';
import { AppConfig } from '@/config/configs';
import * as fs from 'fs';
import * as path from 'path';
import { ApplySubscriptionAccessDto } from './pets.dto';
import { CDN_BASE_URL, QR_BASE_URL } from '@/config/constants';

const FLYER_CDN_PREFIX = 'https://cdn.thefurfinder.com/';

@Injectable()
export class PetsService {
  constructor(
    private readonly db: Kysely<DB>,
    private readonly qrService: QrService,
    private readonly flyerService: FlyerService,
    private readonly imageService: ImageService,
    private readonly configService: ConfigService<AppConfig, true>,
    private readonly logger: Logger,
  ) {}

  private normalizeUuidList(input?: string[]): string[] {
    if (!input || !Array.isArray(input)) return [];
    const deduped = new Set<string>();
    for (const val of input) {
      const id = String(val || '').trim();
      if (id) deduped.add(id);
    }
    return Array.from(deduped);
  }

  async get() {
    return {
      status: false,
      statusCode: 200,
      code: 'device_id_required',
      message: 'Device id is required',
    };
  }

  async applySubscriptionAccessSelection(userId: string, body: ApplySubscriptionAccessDto) {
    console.log(userId);
    const activeProfileIds = this.normalizeUuidList(body.activeProfileIds);
    const activeLostReportIds = this.normalizeUuidList(body.activeLostReportIds);
    const activeFoundReportIds = this.normalizeUuidList(body.activeFoundReportIds);
    const activeReportIds = Array.from(new Set([...activeLostReportIds, ...activeFoundReportIds]));

    try {
      return await this.db.transaction().execute(async (trx) => {
        // 1. Validate profiles ownership
        if (activeProfileIds.length > 0) {
          const ownership = await trx
            .selectFrom('pet_profiles')
            .select((eb) => eb.fn.countAll().as('count'))
            .where('user_id', '=', userId)
            .where('deleted_at', 'is', null)
            .where('id', 'in', activeProfileIds)
            .executeTakeFirst();

          const ownedCount = Number(ownership?.count || 0);
          if (ownedCount !== activeProfileIds.length) {
            throw new BadRequestException('Some selected profiles do not belong to this user.');
          }
        }

        // 2. Validate reports ownership
        if (activeReportIds.length > 0) {
          const ownership = await trx
            .selectFrom('pet_reports')
            .select((eb) => eb.fn.countAll().as('count'))
            .where('user_id', '=', userId)
            .where('deleted_at', 'is', null)
            .where('id', 'in', activeReportIds)
            .executeTakeFirst();

          const ownedCount = Number(ownership?.count || 0);
          if (ownedCount !== activeReportIds.length) {
            throw new BadRequestException('Some selected reports do not belong to this user.');
          }
        }

        // 3. Deactivate all user's profiles
        await trx
          .updateTable('pet_profiles')
          .set({ is_active: false })
          .where('user_id', '=', userId)
          .where('deleted_at', 'is', null)
          .execute();

        // 4. Activate selected profiles
        if (activeProfileIds.length > 0) {
          await trx
            .updateTable('pet_profiles')
            .set({ is_active: true })
            .where('user_id', '=', userId)
            .where('deleted_at', 'is', null)
            .where('id', 'in', activeProfileIds)
            .execute();
        }

        // 5. Deactivate all user's reports
        await trx
          .updateTable('pet_reports')
          .set({ is_active: false })
          .where('user_id', '=', userId)
          .where('deleted_at', 'is', null)
          .execute();

        // 6. Activate selected non-reunited reports
        if (activeReportIds.length > 0) {
          await trx
            .updateTable('pet_reports')
            .set({ is_active: true })
            .where('user_id', '=', userId)
            .where('deleted_at', 'is', null)
            .where('is_reunited', '=', false)
            .where('id', 'in', activeReportIds)
            .execute();
        }

        // 7. Ensure reunited reports remain inactive
        await trx
          .updateTable('pet_reports')
          .set({ is_active: false })
          .where('user_id', '=', userId)
          .where('deleted_at', 'is', null)
          .where('is_reunited', '=', true)
          .execute();

        return {
          status: true,
          statusCode: 200,
          data: {
            activeProfileIds,
            activeLostReportIds,
            activeFoundReportIds,
          },
        };
      });
    } catch (error) {
      if (error instanceof BadRequestException) {
        throw error;
      }
      this.logger.error('Failed to apply subscription access selection', error);
      throw new InternalServerErrorException('Failed to apply subscription access selection');
    }
  }

  async generateFlyer(body: { petId: string; regenerate?: boolean }) {
    const { petId, regenerate } = body;

    if (!petId) {
      throw new BadRequestException('petId is required');
    }

    try {
      // 1. Check existing flyer unless regenerating — only short-circuit on a
      // real CDN URL, matching Express (a legacy local-path row falls through
      // to regenerate below).
      if (!regenerate) {
        const existing = await this.db
          .selectFrom('pet_flyers')
          .select('flyer_url')
          .where('pet_id', '=', petId as any)
          .executeTakeFirst();

        if (existing?.flyer_url?.startsWith(FLYER_CDN_PREFIX)) {
          this.logger.log(`Flyer already exists for pet ${petId}, returning existing URL.`);
          return {
            status: true,
            statusCode: 200,
            data: { url: existing.flyer_url },
          };
        }
      }

      // 2. Fetch pet report
      const pet = await this.db
        .selectFrom('pet_reports')
        .selectAll()
        .where((eb) => eb.or([eb('id', '=', petId as any), eb('pet_id', '=', petId as any)]))
        .where('deleted_at', 'is', null)
        .orderBy('created_at', 'desc')
        .executeTakeFirst();

      if (!pet) {
        throw new NotFoundException('Pet report not found');
      }

      // 4. Resolve image base URL
      const appUrl = this.configService.get('app.url', { infer: true });
      const petImage = pet.photo_uri
        ? pet.photo_uri.startsWith('http')
          ? pet.photo_uri
          : `${appUrl.replace(/\/$/, '')}/${pet.photo_uri.replace(/^\//, '')}`
        : `${CDN_BASE_URL}/assets/img/favicon.png`;

      // 5. Generate QR Code (non-I/O DataURL optimization)
      const qrUrl = `${QR_BASE_URL}/pets/${petId}`;
      const qrImageBase64 = await this.qrService.generateQrCodeDataUrl(qrUrl);

      // 6. Assemble Flyer Data
      const flyerData = {
        name: pet.pet_name || 'Unknown',
        breed: pet.breed || 'Unknown',
        color: pet.color || 'Unknown',
        size: pet.size || 'Unknown',
        lastSeen: pet.last_seen_date ? new Date(pet.last_seen_date).toLocaleDateString() : 'Unknown',
        location: pet.location_name || 'Unknown',
        description: pet.description || '',
        ownerName: pet.contact_name || 'Not Disclosed',
        phone: pet.contact_phone || 'xxx xxx xxxx',
        petImage,
        pet_type: pet.pet_type || 'Unknown',
        qrImage: qrImageBase64, // Directly load data URL in node-canvas!
      };

      this.logger.log(`Drawing flyer for pet report ${pet.id} (status: ${pet.status})`);

      // 7. Render Flyer Canvas
      let buffer: Buffer;
      if (pet.status === 'lost') {
        buffer = await this.flyerService.generateMissingFlyer(flyerData);
      } else if (pet.status === 'found') {
        buffer = await this.flyerService.generateFoundFlyer(flyerData);
      } else {
        throw new BadRequestException(`Invalid pet report status: ${pet.status}`);
      }

      // 8. Keep the previous URL so legacy local files can be cleaned up after
      // the new CDN URL is safely stored.
      const oldFlyer = await this.db
        .selectFrom('pet_flyers')
        .select('flyer_url')
        .where('pet_id', '=', petId as any)
        .executeTakeFirst();

      // 9. Upload new flyer to the CDN
      const fileName = `${petId}_${Date.now()}.png`;
      const [flyerUrl] = await this.imageService.uploadToCDN([
        { filename: fileName, path: 'flyers', file: buffer },
      ]);

      if (!flyerUrl) {
        throw new Error('CDN upload completed without returning a flyer URL.');
      }

      // 10. Upsert into database
      await this.db
        .insertInto('pet_flyers')
        .values({
          pet_id: petId,
          flyer_url: flyerUrl,
          updated_at: new Date(),
        })
        .onConflict((oc) =>
          oc.column('pet_id').doUpdateSet({
            flyer_url: flyerUrl,
            updated_at: new Date(),
          }),
        )
        .execute();

      // Clean up a legacy locally-stored flyer file now that the CDN URL is saved.
      const previousFlyerUrl = oldFlyer?.flyer_url || '';
      if (previousFlyerUrl.startsWith('/uploads/flyers/')) {
        const flyersDir = path.join(process.cwd(), 'public/uploads/flyers');
        const oldPath = path.join(flyersDir, path.basename(previousFlyerUrl));
        try {
          await fs.promises.unlink(oldPath);
        } catch {
          // Ignore delete failure if old file didn't exist
        }
      }

      this.logger.log(`Flyer successfully generated and stored at: ${flyerUrl}`);

      return {
        status: true,
        statusCode: 200,
        data: { url: flyerUrl },
      };
    } catch (error) {
      if (error instanceof NotFoundException || error instanceof BadRequestException) {
        throw error;
      }
      this.logger.error('Failed to generate flyer', error);
      throw new InternalServerErrorException('Failed to generate flyer');
    }
  }
}
