import { HttpStatus, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import {
  DatabaseService,
  envConfig,
  ErrorResponse,
  HashService,
  logger,
  MailService,
  Status,
  UploadService,
} from '../../config';
import { AppString } from '../../constant/app-string';
import { createFieldError, createGeneralError, getOtp } from '../../utils';
import { AccessControlService } from '../access-control/access-control.service';
import { ConfigurationsService } from '../configurations/configurations.service';
import {
  changePasswordDto,
  resetOtpDto,
  resetPasswordDto,
} from '../shared/shared.dto';
import {
  authResponseDto,
  authResponseSchema,
  createOwnerDto,
  ownerResponseDto,
  ownerResponseSchema,
  signinOwnerDto,
  updateOwnerDto,
} from './owner.dto';

@Injectable()
export class OwnerService {
  constructor(
    private readonly db: DatabaseService,
    private readonly configuration: ConfigurationsService,
    private readonly hashService: HashService,
    private readonly jwt: JwtService,
    private readonly mailService: MailService,
    private readonly uploadService: UploadService,
    private readonly accessControlService: AccessControlService,
  ) {}

  // Example: Create an owner
  async createOwner(
    data: createOwnerDto,
  ): Promise<{ data: authResponseDto; message: string }> {
    try {
      const existingOwner = await this.db.owner.findFirst({
        where: {
          OR: [{ email: data.email }, { username: data.username }],
        },
      });

      if (existingOwner) {
        throw createFieldError(
          'root',
          'An owner already exists. Multiple owners are not allowed.',
          HttpStatus.BAD_REQUEST,
        );
      }

      const hashedPassword = await this.hashService.hashPassword(data.password);
      const otp = getOtp();
      const { profile } = envConfig().server;

      if (!data.email) {
        throw createFieldError('email', 'Email is required');
      }

      const owner = await this.db.owner.upsert({
        where: {
          email: data.email,
        },
        create: {
          name: data.name,
          username: data.username,
          email: data.email,
          password: hashedPassword,
          otp,
          status: Status.unverified,
          image: {
            file_name: profile,
            original_name: 'profile.webp',
            file_size: 8192,
            mime_type: 'image/webp',
          },
        },
        update: {
          otp,
          status: Status.unverified,
        },
      });

      const { data: parsedOwner } = authResponseSchema.safeParse(owner);

      this.mailService.sendMail(
        'otp-mail',
        data.email,
        {
          name: data?.name,
          otp,
        },
        'Welcome to Team Tracker',
      );

      return {
        data: parsedOwner,
        message: AppString.CONFIRMATION_OTP_SENT,
      };
    } catch (error) {
      logger.error(`Error in resendOtp: ${String(error)}`);
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  // resend otp
  async resendOtp(email: string): Promise<{
    message: string;
    data: string;
    status: number;
    details: null;
  }> {
    try {
      const owner = await this.db.owner.findFirst({
        where: {
          email,
          status: Status.unverified,
        },
      });
      if (!owner) {
        throw createFieldError('email', AppString.OWNER_NOT_FOUND_EMAIL, 404);
      }
      const otp = getOtp();
      await this.db.owner.update({
        where: {
          email,
        },
        data: {
          otp,
        },
      });
      this.mailService.sendMail(
        'otp-mail',
        email,
        { name: owner?.name, otp },
        'Resend OTP for Team Tracker',
      );
      return {
        message: AppString.CONFIRMATION_OTP_SENT,
        data: email,
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      logger.error(`Error in resendOtp: ${String(error)}`);
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  // verify otp
  async verifyOtp(
    email: string,
    otp: string,
  ): Promise<{
    data: authResponseDto;
    message: string;
    status: number;
    details: null;
  }> {
    try {
      const owner = await this.db.owner.findFirst({
        where: { email, status: Status.unverified },
      });

      if (!owner) {
        throw createFieldError('email', AppString.OWNER_NOT_FOUND_EMAIL, 404);
      }

      const OTP_TTL_MS = 2 * 60 * 1000;
      if (owner.updated_at < new Date(Date.now() - OTP_TTL_MS)) {
        throw createFieldError('otp', AppString.INVALID_EXPIRED_OTP);
      }

      if (owner.otp !== otp) {
        throw createFieldError('otp', 'Invalid OTP');
      }

      const [updatedOwner, token] = await Promise.all([
        this.db.owner.update({
          where: { email },
          data: { status: Status.active, otp: null },
        }),
        this.configuration.createDefaultConfiguration(owner.id),
        this.accessControlService.ensureDefaultRoles(owner.id),
      ]).then(async ([updated]) => {
        const accessRoleId =
          await this.accessControlService.resolveAccessRoleIdBySlug(
            owner.id,
            updated.role ?? 'owner',
          );
        const jwtToken = await this.jwt.signAsync(
          {
            sub: owner.id,
            owner_id: owner.id,
            email: owner.email,
            role: updated.role,
            access_role_id: accessRoleId,
          },
          { expiresIn: '7d' },
        );
        return [updated, jwtToken] as const;
      });

      const { data: parsedOwner } = authResponseSchema.safeParse({
        ...updatedOwner,
        access_token: token,
      });

      return {
        data: parsedOwner,
        message: 'OTP verified successfully',
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      if (error instanceof ErrorResponse) throw error;
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  // sign in owner
  async signin(
    data: signinOwnerDto,
  ): Promise<{ data: authResponseDto; message: string }> {
    try {
      // const owner = await this.db.owner.findFirst({
      //   where: {
      //     OR: [{ email: data?.email }, { username: data?.username }],
      //     status: Status.active,
      //   },
      // });
      // logger.error(
      //   `Signin attempt for email/username: ${JSON.stringify(owner)}`,
      // );
      const orConditions = [];
      const cleanEmail = data?.email?.trim()?.toLowerCase();
      const cleanUsername = data?.username?.trim();

      if (cleanEmail) {
        orConditions.push({ email: cleanEmail, status: Status.active });
      }
      if (cleanUsername) {
        orConditions.push({
          username: cleanUsername,
          status: Status.active,
        });
      }

      if (orConditions.length === 0) {
        logger.error('owner not found1 ' + orConditions);

        throw createGeneralError(
          AppString.INVALID_CREDENTIALS,
          'root',
          HttpStatus.EXPECTATION_FAILED,
        );
      }

      const owner = await this.db.owner.findFirst({
        where: {
          OR: orConditions,
        },
      });
      // throw general error wheather owner is not found or password is invalid to avoid giving hints to attackers

      if (!owner) {
        logger.error(AppString.INVALID_CREDENTIALS);
        throw createGeneralError(
          AppString.INVALID_CREDENTIALS,
          'root',
          HttpStatus.EXPECTATION_FAILED,
        );
      }

      const isPasswordValid = await this.hashService.comparePasswords(
        data?.password,
        owner.password,
      );
      if (!isPasswordValid) {
        logger.error('owner not found2 ' + isPasswordValid);
        throw createGeneralError(
          AppString.INVALID_CREDENTIALS,
          'root',
          HttpStatus.EXPECTATION_FAILED,
        );
      }
      logger.error(`Signin attempt isPasswordValid: ${isPasswordValid}`);
      await this.accessControlService.ensureDefaultRoles(owner.id);
      const accessRoleId =
        await this.accessControlService.resolveAccessRoleIdBySlug(
          owner.id,
          owner.role ?? 'owner',
        );
      const token = await this.jwt.signAsync(
        {
          sub: owner.id,
          owner_id: owner.id,
          email: owner.email,
          role: owner?.role,
          access_role_id: accessRoleId,
        },
        { expiresIn: '7d' },
      );
      const { data: parsedOwner } = authResponseSchema.safeParse({
        ...owner,
        access_token: token,
      });
      return {
        data: parsedOwner,
        message: AppString.SIGNIN_SUCCESSFULL,
      };
    } catch (error) {
      logger.error('owner not found3 ' + error);
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  // update owner
  async updateOwner(
    data: updateOwnerDto,
    id: string,
  ): Promise<{ data: ownerResponseDto; message: string }> {
    try {
      logger.info(
        `Received update request for owner ID: ${id} with data: ${JSON.stringify(data)}`,
      );
      const owner = await this.db.owner.findFirst({
        where: {
          id: id,
          status: Status.active,
        },
      });
      if (!owner) {
        throw createFieldError('id', AppString.OWNER_NOT_FOUND, 404);
      }
      let image = data?.image;
      if (image) {
        image = await this.uploadService.moveImage(image, 'owners');
      }
      const updatedOwner = await this.db.owner.update({
        where: {
          id,
        },
        data: {
          ...data,
          image: image ?? owner.image,
        },
      });
      const { data: parsedOwner } = ownerResponseSchema.safeParse(updatedOwner);
      return {
        data: parsedOwner,
        message: AppString.OWNER_UPDATED_SUCCESSFULLY,
      };
    } catch (error) {
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  // forgot password
  async forgotPassword(email: string): Promise<{
    message: string;
    data: string;
    status: number;
    details: null;
  }> {
    try {
      const owner = await this.db.owner.findFirst({
        where: {
          email,
          status: Status.active,
        },
      });
      if (!owner) {
        throw createFieldError('email', AppString.OWNER_NOT_FOUND_EMAIL, 404);
      }
      const otp = getOtp();
      await this.db.owner.update({
        where: {
          email,
        },
        data: {
          otp,
        },
      });
      this.mailService.sendMail(
        'otp-mail',
        email,
        { name: owner?.name, otp },
        'Reset Password OTP for Team Tracker',
      );
      return {
        message: AppString.PASSWORD_RESET_OTP_SENT,
        data: email,
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  // reset password
  async resetVerifyOtp(data: resetOtpDto): Promise<{ message: string }> {
    try {
      const owner = await this.db.owner.findFirst({
        where: {
          email: data?.email,
          status: Status.active,
        },
      });
      if (!owner) {
        throw createFieldError('email', AppString.OWNER_NOT_FOUND_EMAIL, 404);
      }
      if (owner.updated_at < new Date(Date.now() - 2 * 60 * 1000)) {
        // Check if OTP is older than 2 minutes
        throw createFieldError('otp', AppString.INVALID_EXPIRED_OTP);
      }
      if (owner?.otp !== data?.otp) {
        throw createFieldError('otp', 'Invalid OTP');
      }
      return { message: 'OTP verify successfully' };
    } catch (error) {
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async resetPassword(data: resetPasswordDto): Promise<{ message: string }> {
    try {
      const owner = await this.db.owner.findFirst({
        where: {
          email: data?.email,
          status: Status.active,
        },
      });
      if (!owner) {
        throw createFieldError('email', AppString.OWNER_NOT_FOUND_EMAIL, 404);
      }
      const hashedPassword = await this.hashService.hashPassword(
        data?.password,
      );
      logger.error(`Signin attempt isPasswordValid: ${hashedPassword}`);

      await this.db.owner.update({
        where: {
          email: data?.email,
        },
        data: {
          password: hashedPassword,
          otp: null,
        },
      });
      return { message: 'Password reset successfully' };
    } catch (error) {
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  // change password
  async changePassword(
    ownerId: string,
    data: changePasswordDto,
  ): Promise<{
    message: string;
    data: null;
    status: number;
    details: null;
  }> {
    try {
      const owner = await this.db.owner.findFirst({
        where: {
          id: ownerId,
          status: Status.active,
        },
      });
      if (!owner) {
        throw createFieldError('ownerId', AppString.OWNER_NOT_FOUND, 404);
      }
      const isPasswordValid = await this.hashService.comparePasswords(
        data?.current_password,
        owner.password,
      );
      if (!isPasswordValid) {
        throw createFieldError(
          'currentPassword',
          AppString.INVALID_CREDENTIALS,
          HttpStatus.EXPECTATION_FAILED,
        );
      }
      const hashedPassword = await this.hashService.hashPassword(
        data?.new_password,
      );
      await this.db.owner.update({
        where: {
          id: ownerId,
        },
        data: {
          password: hashedPassword,
        },
      });
      return {
        message: AppString.PASSWORD_CHANGE_SUCCESSFULL,
        data: null,
        status: HttpStatus.OK,
        details: null,
      };
    } catch (error) {
      if (error instanceof ErrorResponse) {
        throw error;
      }
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }
}
