import {
  Injectable,
  NotFoundException,
  ForbiddenException,
  BadRequestException,
  InternalServerErrorException,
} from '@nestjs/common';
import { Kysely } from 'kysely';
import { DB } from '@/database/database.type';
import { Logger } from 'pino-nestjs';
import { DeviceService } from '../devices/device.service';
import { PushService } from '../push/push.service';

@Injectable()
export class ClaimsService {
  constructor(
    private readonly db: Kysely<DB>,
    private readonly deviceService: DeviceService,
    private readonly pushService: PushService,
    private readonly logger: Logger,
  ) {}

  private async sendPushToUserDevices(
    userId: string,
    title: string,
    message: string,
    data: Record<string, any>,
  ) {
    try {
      const pushTokens = await this.deviceService.getUserPushTokens(userId);
      for (const token of pushTokens) {
        await this.pushService.sendPushNotification(token, userId, title, message, data);
      }
    } catch (err) {
      this.logger.error('[Push Notification Error]', err);
    }
  }

  async getClaims(userId: string, statusQuery?: string, typeQuery?: string) {
    try {
      const status = statusQuery || 'pending';
      const type = typeQuery || 'received';
      const isAll = status === 'all';

      let result: any[];

      if (type === 'sent') {
        let query = this.db
          .selectFrom('claim_requests as c')
          .innerJoin('pet_reports as f', (join) =>
            join.onRef('f.id', '=', 'c.found_report_id').on('f.deleted_at', 'is', null),
          )
          .innerJoin('users as u', (join) =>
            join.onRef('u.id', '=', 'c.found_user_id').on('u.deleted_at', 'is', null),
          )
          .select([
            'c.id',
            'c.lost_report_id',
            'c.found_report_id',
            'c.claimer_user_id',
            'c.found_user_id',
            'c.status',
            'c.created_at',
            'f.pet_name as found_pet_name',
            'f.breed',
            'f.pet_type',
            'f.photo_uri as found_pet_photo',
            'f.location_name as found_location',
            'u.display_name as owner_name',
          ])
          .where('c.claimer_user_id', '=', userId)
          .where('c.deleted_at', 'is', null);

        if (!isAll) {
          query = query.where('c.status', '=', status);
        }

        result = await query.orderBy('c.created_at', 'desc').execute();
      } else {
        let query = this.db
          .selectFrom('claim_requests as c')
          .innerJoin('pet_reports as l', (join) =>
            join.onRef('l.id', '=', 'c.lost_report_id').on('l.deleted_at', 'is', null),
          )
          .innerJoin('users as u', (join) =>
            join.onRef('u.id', '=', 'c.claimer_user_id').on('u.deleted_at', 'is', null),
          )
          .select([
            'c.id',
            'c.lost_report_id',
            'c.found_report_id',
            'c.claimer_user_id',
            'c.found_user_id',
            'c.status',
            'c.created_at',
            'l.pet_name as lost_pet_name',
            'l.photo_uri as lost_pet_photo',
            'l.location_name as lost_location',
            'u.display_name as claimer_name',
            'u.email as claimer_email',
          ])
          .where('c.found_user_id', '=', userId)
          .where('c.deleted_at', 'is', null);

        if (!isAll) {
          query = query.where('c.status', '=', status);
        }

        result = await query.orderBy('c.created_at', 'desc').execute();
      }

      return {
        status: true,
        statusCode: 200,
        data: result,
      };
    } catch (error) {
      this.logger.error('Failed to get claims', error);
      throw new InternalServerErrorException('Failed to retrieve claim requests');
    }
  }

  async approveClaim(claimId: string, userId: string) {
    try {
      const claim = await this.db
        .selectFrom('claim_requests')
        .selectAll()
        .where('id', '=', claimId)
        .where('deleted_at', 'is', null)
        .executeTakeFirst();

      if (!claim) {
        throw new NotFoundException('Claim request not found');
      }

      if (claim.found_user_id !== userId) {
        throw new ForbiddenException('Not authorized to approve this claim');
      }

      if (claim.status !== 'pending') {
        throw new BadRequestException(`Claim is already ${claim.status}`);
      }

      await this.db
        .updateTable('claim_requests')
        .set({ status: 'approved' })
        .where('id', '=', claimId)
        .where('deleted_at', 'is', null)
        .execute();

      const title = 'Claim Approved! 🎉';
      const message = 'Your claim was approved! Your pet may be reunited.';

      // Insert local notification
      await this.db
        .insertInto('notifications')
        .values({
          user_id: claim.claimer_user_id,
          type: 'claim_approved',
          title,
          message,
          report_id: claim.lost_report_id,
          read: false,
          created_at: new Date(),
        })
        .execute();

      // Trigger background push
      this.sendPushToUserDevices(claim.claimer_user_id, title, message, {
        type: 'claim_approved',
        report_id: claim.lost_report_id,
        claimId,
      }).catch((err) => this.logger.error('[Approve] Push error:', err));

      return {
        status: true,
        statusCode: 200,
        data: { success: true },
      };
    } catch (error) {
      if (
        error instanceof NotFoundException ||
        error instanceof ForbiddenException ||
        error instanceof BadRequestException
      ) {
        throw error;
      }
      this.logger.error('Failed to approve claim', error);
      throw new InternalServerErrorException('Failed to approve claim request');
    }
  }

  async rejectClaim(claimId: string, userId: string) {
    try {
      const claim = await this.db
        .selectFrom('claim_requests')
        .selectAll()
        .where('id', '=', claimId)
        .where('deleted_at', 'is', null)
        .executeTakeFirst();

      if (!claim) {
        throw new NotFoundException('Claim request not found');
      }

      if (claim.found_user_id !== userId) {
        throw new ForbiddenException('Not authorized to reject this claim');
      }

      await this.db
        .updateTable('claim_requests')
        .set({ status: 'rejected' })
        .where('id', '=', claimId)
        .where('deleted_at', 'is', null)
        .execute();

      const title = 'Claim Rejected';
      const message = 'Your claim was not approved.';

      // Insert local notification
      await this.db
        .insertInto('notifications')
        .values({
          user_id: claim.claimer_user_id,
          type: 'claim_rejected',
          title,
          message,
          report_id: claim.lost_report_id,
          read: false,
          created_at: new Date(),
        })
        .execute();

      // Trigger background push
      this.sendPushToUserDevices(claim.claimer_user_id, title, message, {
        type: 'claim_rejected',
        report_id: claim.lost_report_id,
        claimId,
      }).catch((err) => this.logger.error('[Reject] Push error:', err));

      return {
        status: true,
        statusCode: 200,
        data: { success: true },
      };
    } catch (error) {
      if (error instanceof NotFoundException || error instanceof ForbiddenException) {
        throw error;
      }
      this.logger.error('Failed to reject claim', error);
      throw new InternalServerErrorException('Failed to reject claim request');
    }
  }

  async createClaim(foundReportId: string, lostReportId: string, userId: string) {
    try {
      // 1. Validate user owns lostReportId
      const lostReport = await this.db
        .selectFrom('pet_reports')
        .select(['id', 'user_id', 'pet_name'])
        .where('id', '=', lostReportId)
        .where('deleted_at', 'is', null)
        .executeTakeFirst();

      if (!lostReport) {
        throw new NotFoundException('Lost report not found');
      }

      if (lostReport.user_id !== userId) {
        throw new ForbiddenException('Not authorized for this lost report');
      }

      // 2. Validate target report is 'found'
      const foundReport = await this.db
        .selectFrom('pet_reports')
        .select(['id', 'user_id', 'status', 'pet_name'])
        .where('id', '=', foundReportId)
        .where('deleted_at', 'is', null)
        .executeTakeFirst();

      if (!foundReport) {
        throw new NotFoundException('Found report not found');
      }

      if (foundReport.status !== 'found') {
        throw new BadRequestException('Target report is not a found pet');
      }

      // 3. Check if claim already exists
      const existing = await this.db
        .selectFrom('claim_requests')
        .select('id')
        .where('lost_report_id', '=', lostReportId)
        .where('found_report_id', '=', foundReportId)
        .where('status', '=', 'pending')
        .where('deleted_at', 'is', null)
        .executeTakeFirst();

      if (existing) {
        throw new BadRequestException('Claim request already pending');
      }

      // 4. Create claim request
      const result = await this.db
        .insertInto('claim_requests')
        .values({
          lost_report_id: lostReportId,
          found_report_id: foundReportId,
          claimer_user_id: userId,
          found_user_id: foundReport.user_id,
          status: 'pending',
          created_at: new Date(),
        })
        .returningAll()
        .executeTakeFirstOrThrow();

      // 5. Notify found report owner
      const title = 'New Claim Request';
      const message = `Someone thinks the ${foundReport.pet_name} you found might belong to them.`;

      await this.db
        .insertInto('notifications')
        .values({
          user_id: foundReport.user_id,
          type: 'claim_received',
          title,
          message,
          report_id: foundReportId,
          read: false,
          created_at: new Date(),
        })
        .execute();

      // Trigger background push
      this.sendPushToUserDevices(foundReport.user_id, title, message, {
        type: 'claim_received',
        report_id: foundReportId,
        claimId: result.id,
      }).catch((err) => this.logger.error('[Claim] Push error:', err));

      return {
        status: true,
        statusCode: 200,
        data: result,
      };
    } catch (error) {
      if (
        error instanceof NotFoundException ||
        error instanceof ForbiddenException ||
        error instanceof BadRequestException
      ) {
        throw error;
      }
      this.logger.error('Failed to create claim', error);
      throw new InternalServerErrorException('Failed to create claim request');
    }
  }

  async fetClaimForPet(petId: string, userId: string, typeQuery?: string) {
    try {
      const type = typeQuery || 'sent';
      let result: any[];

      if (type === 'received') {
        result = await this.db
          .selectFrom('claim_requests')
          .selectAll()
          .where('found_report_id', '=', petId)
          .where('found_user_id', '=', userId)
          .where('deleted_at', 'is', null)
          .execute();
      } else {
        result = await this.db
          .selectFrom('claim_requests')
          .selectAll()
          .where('found_report_id', '=', petId)
          .where('claimer_user_id', '=', userId)
          .where('deleted_at', 'is', null)
          .execute();
      }

      return {
        status: true,
        statusCode: 200,
        data: result,
      };
    } catch (error) {
      this.logger.error('Failed to fetch claim for pet', error);
      throw new InternalServerErrorException('Failed to retrieve claim requests for pet');
    }
  }
}
