import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from "@nestjs/common"
import { Observable } from "rxjs"
import { failureResponse } from "../common/response/response"
import { code } from "../common/response/response.code"
import { messageKey } from "../constants/message-keys"
import { validationMessage } from "../utils/helpers"
import { verifyJwtToken, JwtPayload } from "../utils/jwt"
import { AuthRepository } from "../modules/auth/repositories/auth.repository"
import { UserBlacklistedTokenRepository } from "../modules/auth/repositories/user-blacklisted-token.repository"
import { EmployeeRepository } from "../modules/employees/repositories/employee.repository"
import { RoleService } from "../modules/role/role.service"
import { CompanySubscriptionRepository } from "../modules/company/repositories/company-subscription.repository"
import { SubscriptionStatus } from "../modules/company/entities/company-subscription.entity"

@Injectable()
export class AuthGuardMiddleware implements CanActivate {
  constructor(
    private readonly authRepository: AuthRepository,
    private readonly userBlacklistedTokenRepository: UserBlacklistedTokenRepository,
    private readonly employeeRepository: EmployeeRepository,
    private readonly roleService: RoleService,
    private readonly companySubscriptionRepository: CompanySubscriptionRepository,
  ) {}

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest()
    return this.validateRequest(request)
  }

  async validateRequest(req: any): Promise<boolean> {
    try {
      // Extract the token from the request headers
      const token = req.headers["authorization"]

      if (!token) {
        throw new UnauthorizedException(
          failureResponse(
            code.UNAUTHORIZED,
            validationMessage(messageKey.invalid_token),
          ),
        )
      }

      // Verify JWT token signature and expiry
      let decoded: JwtPayload | null = null

      try {
        decoded = verifyJwtToken(token)
      } catch (jwtError: any) {
        // Handle token expiration specifically

        if (jwtError.name === "TokenExpiredError") {
          throw new UnauthorizedException({
            status: false,
            code: 1002,
            message: "Access token expired",
          })
        }
        // For other JWT errors, treat as invalid token
        decoded = null
      }

      if (!decoded) {
        throw new UnauthorizedException({
          status: false,
          code: 1002,
          message: "Access token expired",
        })
      }

      // Check if token is blacklisted
      const blacklistedToken =
        await this.userBlacklistedTokenRepository.getByParams({
          where: { token },
          findOne: true,
        })

      if (blacklistedToken) {
        throw new UnauthorizedException(
          failureResponse(
            code.UNAUTHORIZED,
            validationMessage(messageKey.invalid_token),
          ),
        )
      }

      // Verify user exists and is active
      const user: any = await this.authRepository.getByParams({
        where: { id: decoded.user_id },
        findOne: true,
      })

      if (!user) {
        throw new UnauthorizedException(
          failureResponse(
            code.UNAUTHORIZED,
            validationMessage(messageKey.user_not_found),
          ),
        )
      }

      // Check if user account is active
      if (user.status === 0) {
        throw new UnauthorizedException(
          failureResponse(
            code.UNAUTHORIZED,
            validationMessage(messageKey.user_account_inactive),
          ),
        )
      }

      // Check department status for non-master admin users
      if (decoded.employee_id) {
        await this.validateDepartmentStatus(
          decoded.employee_id,
          decoded.role_id,
          decoded.company_id,
        )
      }

      // Check company subscription status for non-master admin users
      if (decoded.company_id) {
        await this.validateCompanySubscription(
          decoded.role_id,
          decoded.company_id,
        )
      }

      // Attach user information to request for use in controllers
      req.user = {
        user_id: decoded.user_id,
        role_id: decoded.role_id,
        company_id: decoded.company_id,
        employee_id: decoded.employee_id,
      }

      // Attach the raw token for services that need it
      req.token = token

      return true
    } catch (error) {
      // Re-throw UnauthorizedException as-is
      if (error instanceof UnauthorizedException) {
        throw error
      }

      // Handle any other unexpected errors
      throw new UnauthorizedException(
        failureResponse(
          code.UNAUTHORIZED,
          validationMessage(messageKey.invalid_token),
        ),
      )
    }
  }

  private async validateDepartmentStatus(
    employeeId: number,
    roleId: number,
    companyId?: number,
  ): Promise<void> {
    // Check if user is master admin - skip department validation
    const masterAdminRole = await this.roleService.findRoleByNameAndCompany(
      "master_admin",
      companyId,
    )
    if (masterAdminRole && masterAdminRole.id === roleId) {
      return // Master admin bypasses department check
    }

    // Fetch employee with department relation
    const employee: any = await this.employeeRepository.getByParams({
      where: { id: employeeId, company_id: companyId },
      relations: ["department"],
      findOne: true,
    })

    if (employee && employee.department) {
      // Check if department status is inactive (0)
      if (employee.department.status === 0) {
        throw new UnauthorizedException({
          status: false,
          code: 1007,
          message: "Your department is inactive. Please contact admin.",
        })
      }
    }
  }

  private async validateCompanySubscription(
    roleId: number,
    companyId: number,
  ): Promise<void> {
    // Check if user is master admin - skip subscription validation
    const masterAdminRole = await this.roleService.findRoleByNameAndCompany(
      "master_admin",
      companyId,
    )
    if (masterAdminRole && masterAdminRole.id === roleId) {
      return // Master admin bypasses subscription check
    }

    // Fetch active company subscription
    const subscription: any =
      await this.companySubscriptionRepository.getByParams({
        where: {
          company_id: companyId,
          status: SubscriptionStatus.ACTIVE,
        },
        findOne: true,
      })

    if (!subscription) {
      throw new UnauthorizedException({
        status: false,
        code: 1008,
        message: "Company subscription not found. Please contact admin.",
      })
    }

    // Check if subscription has expired based on end date
    const currentDate = new Date()
    const subscriptionEndDate = new Date(subscription.subscription_end_date)

    if (subscriptionEndDate < currentDate) {
      throw new UnauthorizedException({
        status: false,
        code: 1008,
        message: "Company subscription has expired. Please contact admin.",
      })
    }
  }
}
