import { HttpStatus, Injectable } from '@nestjs/common';
import { DatabaseService, ErrorResponse, logger } from '../../config';
import { createGeneralError } from '../../utils';
import {
  DeviceChangeFilterDto,
  DeviceChangeRequestResponseDto,
  RequestDeviceChangeDto,
  ReviewDeviceChangeDto,
  deviceChangeRequestListSchema,
  deviceChangeRequestResponseSchema,
  teacherDeviceResponseSchema,
} from './devices.dto';

@Injectable()
export class DevicesService {
  constructor(private readonly db: DatabaseService) {}

  /**
   * Validates device binding or automatically binds device on first login/attendance.
   * Also enforces anti-proxy / anti-buddy punching checks.
   */
  async validateOrBindDevice(
    memberId: string,
    ownerId: string,
    deviceId: string,
    deviceName?: string | null,
    clientIp?: string | null,
  ): Promise<{ valid: boolean; message?: string }> {
    if (!deviceId || !deviceId.trim()) {
      return {
        valid: false,
        message: 'Device ID is required to access attendance.',
      };
    }

    const cleanDeviceId = deviceId.trim();

    // 1. Anti-proxy / shared device check: Check if this device is bound to ANOTHER teacher
    const deviceBoundToOther = await this.db.teacher_devices.findFirst({
      where: {
        owner_id: ownerId,
        device_id: cleanDeviceId,
      },
      include: {
        member: true,
      },
    });

    if (deviceBoundToOther && deviceBoundToOther.member_id !== memberId) {
      const otherName = deviceBoundToOther.member
        ? `${deviceBoundToOther.member.first_name} ${deviceBoundToOther.member.last_name || ''}`.trim()
        : 'another teacher';
      return {
        valid: false,
        message: `Proxy attendance detected: This device is already locked to ${otherName}. Attendance on behalf of other teachers is strictly prohibited.`,
      };
    }

    // 2. Check if this teacher is already bound to a device
    const existingBinding = await this.db.teacher_devices.findFirst({
      where: {
        member_id: memberId,
        owner_id: ownerId,
      },
    });

    // 3. First time login/attendance: Automatically bind device!
    if (!existingBinding) {
      await this.db.teacher_devices.create({
        data: {
          member_id: memberId,
          owner_id: ownerId,
          device_id: cleanDeviceId,
          device_name: deviceName ?? 'Default Device',
          last_ip: clientIp ?? null,
          bound_at: new Date(),
          last_active_at: new Date(),
        },
      });
      logger.info(`Device ${cleanDeviceId} successfully bound to teacher ${memberId}`);
      return { valid: true };
    }

    // 4. Existing device matches -> Update activity and allow
    if (existingBinding.device_id === cleanDeviceId) {
      await this.db.teacher_devices.update({
        where: { id: existingBinding.id },
        data: {
          last_ip: clientIp ?? existingBinding.last_ip,
          last_active_at: new Date(),
          ...(deviceName ? { device_name: deviceName } : {}),
        },
      });
      return { valid: true };
    }

    // 5. Existing device does NOT match -> Block action!
    return {
      valid: false,
      message: `Device mismatch: Your account is locked to a different device (${existingBinding.device_name || 'Registered Device'}). Please submit a Device Change Request to the administrator.`,
    };
  }

  /**
   * Teacher submits a request to switch to a new device
   */
  async requestDeviceChange(
    memberId: string,
    ownerId: string,
    data: RequestDeviceChangeDto,
  ) {
    try {
      const existingBinding = await this.db.teacher_devices.findFirst({
        where: { member_id: memberId, owner_id: ownerId },
      });

      const oldDeviceId = existingBinding ? existingBinding.device_id : 'UNBOUND';

      // Check if there's already a pending request
      const existingPending = await this.db.device_change_requests.findFirst({
        where: {
          member_id: memberId,
          owner_id: ownerId,
          status: 'pending',
        },
      });

      let requestRecord;
      if (existingPending) {
        requestRecord = await this.db.device_change_requests.update({
          where: { id: existingPending.id },
          data: {
            old_device_id: oldDeviceId,
            new_device_id: data.device_id.trim(),
            new_device_name: data.device_name ?? null,
            reason: data.reason.trim(),
            status: 'pending',
          },
        });
      } else {
        requestRecord = await this.db.device_change_requests.create({
          data: {
            member_id: memberId,
            owner_id: ownerId,
            old_device_id: oldDeviceId,
            new_device_id: data.device_id.trim(),
            new_device_name: data.device_name ?? null,
            reason: data.reason.trim(),
            status: 'pending',
          },
        });
      }

      const { data: parsed, error } =
        deviceChangeRequestResponseSchema.safeParse(requestRecord);
      if (error) {
        throw new ErrorResponse(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
      }

      return {
        status: HttpStatus.CREATED,
        message:
          'Device change request submitted successfully. Please wait for admin approval.',
        data: parsed,
      };
    } catch (error) {
      logger.error('Error in requestDeviceChange', error);
      if (error instanceof ErrorResponse) throw error;
      throw new ErrorResponse(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Teacher retrieves their current device info and pending request status
   */
  async getMyDevice(memberId: string, ownerId: string) {
    try {
      const binding = await this.db.teacher_devices.findFirst({
        where: { member_id: memberId, owner_id: ownerId },
        include: { member: true },
      });

      const pendingRequest = await this.db.device_change_requests.findFirst({
        where: { member_id: memberId, owner_id: ownerId, status: 'pending' },
      });

      return {
        status: HttpStatus.OK,
        message: 'Device information retrieved successfully',
        data: {
          binding: binding ? teacherDeviceResponseSchema.safeParse(binding).data : null,
          pending_request: pendingRequest
            ? deviceChangeRequestResponseSchema.safeParse(pendingRequest).data
            : null,
        },
      };
    } catch (error) {
      logger.error('Error in getMyDevice', error);
      if (error instanceof ErrorResponse) throw error;
      throw new ErrorResponse(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Admin lists all device change requests
   */
  async getDeviceChangeRequests(ownerId: string, filter: DeviceChangeFilterDto) {
    try {
      const where: any = { owner_id: ownerId };
      if (filter.status && filter.status !== 'all') {
        where.status = filter.status;
      }
      if (filter.member_id) {
        where.member_id = filter.member_id;
      }

      const requests = await this.db.device_change_requests.findMany({
        where,
        include: { member: true },
        orderBy: { created_at: 'desc' },
      });

      const { data, error } = deviceChangeRequestListSchema.safeParse(requests);
      if (error) {
        throw new ErrorResponse(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
      }

      return {
        status: HttpStatus.OK,
        message: 'Device change requests retrieved successfully',
        data,
      };
    } catch (error) {
      logger.error('Error in getDeviceChangeRequests', error);
      if (error instanceof ErrorResponse) throw error;
      throw new ErrorResponse(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Admin reviews (approve/reject) a device change request
   */
  async reviewDeviceChangeRequest(
    requestId: string,
    ownerId: string,
    reviewerId: string,
    body: ReviewDeviceChangeDto,
  ) {
    try {
      const request = await this.db.device_change_requests.findFirst({
        where: { id: requestId, owner_id: ownerId },
      });

      if (!request) {
        throw new ErrorResponse('Device change request not found', HttpStatus.NOT_FOUND);
      }

      if (request.status !== 'pending') {
        throw new ErrorResponse(
          `Request has already been ${request.status}`,
          HttpStatus.BAD_REQUEST,
        );
      }

      if (body.status === 'approved') {
        // Update teacher's bound device
        const existingDevice = await this.db.teacher_devices.findFirst({
          where: { member_id: request.member_id, owner_id: ownerId },
        });

        if (existingDevice) {
          await this.db.teacher_devices.update({
            where: { id: existingDevice.id },
            data: {
              device_id: request.new_device_id,
              device_name: request.new_device_name ?? existingDevice.device_name,
              bound_at: new Date(),
              last_active_at: new Date(),
            },
          });
        } else {
          await this.db.teacher_devices.create({
            data: {
              member_id: request.member_id,
              owner_id: ownerId,
              device_id: request.new_device_id,
              device_name: request.new_device_name ?? 'New Device',
              bound_at: new Date(),
              last_active_at: new Date(),
            },
          });
        }
      }

      const updatedRequest = await this.db.device_change_requests.update({
        where: { id: requestId },
        data: {
          status: body.status,
          reviewed_by_id: reviewerId,
          reviewed_at: new Date(),
          admin_note: body.admin_note ?? null,
        },
      });

      return {
        status: HttpStatus.OK,
        message: `Device change request ${body.status} successfully`,
        data: deviceChangeRequestResponseSchema.safeParse(updatedRequest).data,
      };
    } catch (error) {
      logger.error('Error in reviewDeviceChangeRequest', error);
      if (error instanceof ErrorResponse) throw error;
      throw new ErrorResponse(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Admin resets device lock for a teacher (allowing them to bind next device)
   */
  async resetDeviceBinding(memberId: string, ownerId: string) {
    try {
      const binding = await this.db.teacher_devices.findFirst({
        where: { member_id: memberId, owner_id: ownerId },
      });

      if (!binding) {
        throw new ErrorResponse('No device binding found for this member', HttpStatus.NOT_FOUND);
      }

      await this.db.teacher_devices.delete({
        where: { id: binding.id },
      });

      return {
        status: HttpStatus.OK,
        message: 'Device binding reset successfully. Teacher can now connect with a new device.',
      };
    } catch (error) {
      logger.error('Error in resetDeviceBinding', error);
      if (error instanceof ErrorResponse) throw error;
      throw new ErrorResponse(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }
}
