import {
  Injectable,
  HttpStatus,
  ConflictException,
  NotFoundException,
  UnauthorizedException,
  BadRequestException,
} from '@nestjs/common';
import { Kysely } from 'kysely';
import { DB } from 'src/database/database.type';
import { Logger } from 'pino-nestjs';
import { AuthHelper } from '../../utils/auth.helpers';
import { AdminLoginDto, CreateAdminDto, UpdateAdminDto, ResetAdminPasswordDto } from './admins.dto';
import { DASHBOARD_ADMIN_TOKEN_TYPE } from './admin-auth.guard';

const ADMIN_PUBLIC_COLUMNS = [
  'id',
  'email',
  'display_name',
  'role',
  'is_active',
  'last_login_at',
  'created_at',
  'updated_at',
] as const;

@Injectable()
export class AdminsService {
  constructor(
    private db: Kysely<DB>,
    private readonly authHelper: AuthHelper,
    private logger: Logger,
  ) {}

  private getSessionToken(admin: { id: string; email: string; display_name: string; role: string }) {
    return this.authHelper.generateToken({
      id: admin.id,
      email: admin.email,
      display_name: admin.display_name,
      role: admin.role,
      type: DASHBOARD_ADMIN_TOKEN_TYPE,
    });
  }

  async login(data: AdminLoginDto) {
    const email = data.email.trim().toLowerCase();

    const admin = await this.db
      .selectFrom('admins')
      .select([...ADMIN_PUBLIC_COLUMNS, 'password_hash'])
      .where('email', '=', email)
      .where('deleted_at', 'is', null)
      .executeTakeFirst();

    if (!admin) {
      throw new UnauthorizedException('Invalid credentials');
    }

    const validPassword = await this.authHelper.verifyPassword(data.password, admin.password_hash);
    if (!validPassword) {
      throw new UnauthorizedException('Invalid credentials');
    }

    if (!admin.is_active) {
      throw new UnauthorizedException('This admin account has been deactivated');
    }

    await this.db
      .updateTable('admins')
      .set({ last_login_at: new Date(), updated_at: new Date() })
      .where('id', '=', admin.id)
      .execute();

    const { password_hash: _passwordHash, ...publicAdmin } = admin;

    return {
      status: true,
      statusCode: HttpStatus.OK,
      data: { admin: publicAdmin, token: this.getSessionToken(publicAdmin) },
    };
  }

  async me(adminId: string) {
    const admin = await this.db
      .selectFrom('admins')
      .select(ADMIN_PUBLIC_COLUMNS)
      .where('id', '=', adminId)
      .where('deleted_at', 'is', null)
      .executeTakeFirst();

    if (!admin) throw new NotFoundException('Admin not found');

    return { status: true, statusCode: HttpStatus.OK, data: admin };
  }

  async list() {
    const admins = await this.db
      .selectFrom('admins')
      .select(ADMIN_PUBLIC_COLUMNS)
      .where('deleted_at', 'is', null)
      .orderBy('created_at', 'asc')
      .execute();

    return { status: true, statusCode: HttpStatus.OK, data: admins };
  }

  async create(data: CreateAdminDto, createdBy: string) {
    const email = data.email.trim().toLowerCase();

    const existing = await this.db
      .selectFrom('admins')
      .select('id')
      .where('email', '=', email)
      .where('deleted_at', 'is', null)
      .executeTakeFirst();

    if (existing) {
      throw new ConflictException('An admin with this email already exists');
    }

    const passwordHash = await this.authHelper.hashPassword(data.password);

    const admin = await this.db
      .insertInto('admins')
      .values({
        email,
        display_name: data.display_name.trim(),
        password_hash: passwordHash,
        role: data.role,
        created_by: createdBy,
      })
      .returning(ADMIN_PUBLIC_COLUMNS)
      .executeTakeFirstOrThrow();

    this.logger.log(`Admin ${email} created by ${createdBy}`);

    return { status: true, statusCode: HttpStatus.CREATED, data: admin };
  }

  async update(adminId: string, data: UpdateAdminDto, actingAdminId: string) {
    if (adminId === actingAdminId && data.is_active === false) {
      throw new BadRequestException('You cannot deactivate your own account');
    }
    if (adminId === actingAdminId && data.role && data.role !== 'super_admin') {
      throw new BadRequestException('You cannot demote your own account');
    }

    const updates: Record<string, unknown> = { updated_at: new Date() };
    if (data.display_name !== undefined) updates.display_name = data.display_name.trim();
    if (data.role !== undefined) updates.role = data.role;
    if (data.is_active !== undefined) updates.is_active = data.is_active;

    const admin = await this.db
      .updateTable('admins')
      .set(updates)
      .where('id', '=', adminId)
      .where('deleted_at', 'is', null)
      .returning(ADMIN_PUBLIC_COLUMNS)
      .executeTakeFirst();

    if (!admin) throw new NotFoundException('Admin not found');

    return { status: true, statusCode: HttpStatus.OK, data: admin };
  }

  async remove(adminId: string, actingAdminId: string) {
    if (adminId === actingAdminId) {
      throw new BadRequestException('You cannot delete your own account');
    }

    const admin = await this.db
      .updateTable('admins')
      .set({ deleted_at: new Date(), is_active: false, updated_at: new Date() })
      .where('id', '=', adminId)
      .where('deleted_at', 'is', null)
      .returning('id')
      .executeTakeFirst();

    if (!admin) throw new NotFoundException('Admin not found');

    return { status: true, statusCode: HttpStatus.OK, message: 'Admin deleted' };
  }

  async resetPassword(adminId: string, data: ResetAdminPasswordDto, actingAdminId: string) {
    const passwordHash = await this.authHelper.hashPassword(data.newPassword);

    const admin = await this.db
      .updateTable('admins')
      .set({ password_hash: passwordHash, updated_at: new Date() })
      .where('id', '=', adminId)
      .where('deleted_at', 'is', null)
      .returning(['id', 'email'])
      .executeTakeFirst();

    if (!admin) throw new NotFoundException('Admin not found');

    this.logger.log(`Password for admin ${admin.email} reset by ${actingAdminId}`);

    return { status: true, statusCode: HttpStatus.OK, message: 'Password reset successful' };
  }
}
