import { ClassError } from '@tomei/general';
import { PriceCompanyFeedAccessAttr } from '../../interfaces/price-company-feed-access-attr.interface';
import { NonSourceFeedName, PricingLogic } from '../../enum/feed.enum';
import { CompanyFeedAccessRepository } from './company-feed-access.repository';
import { createId, init } from '@paralleldrive/cuid2';

export class CompanyFeedAccess implements PriceCompanyFeedAccessAttr {
  CompanyAccessId?: string;
  CompanyCode: string;
  FeedName: string;
  Status: string;
  PricingLogic: PricingLogic;
  CreatedById: number;
  CreatedAt?: Date;
  UpdatedById: number;
  UpdatedAt?: Date;

  protected static _Repo = new CompanyFeedAccessRepository();

  constructor(accessAttr?: PriceCompanyFeedAccessAttr) {
    if (accessAttr) {
      this.CompanyAccessId = accessAttr.CompanyAccessId;
      this.CompanyCode = accessAttr.CompanyCode;
      this.FeedName = accessAttr.FeedName;
      this.Status = accessAttr.Status;
      this.PricingLogic = accessAttr.PricingLogic;
      this.CreatedById = accessAttr.CreatedById;
      this.CreatedAt = accessAttr.CreatedAt;
      this.UpdatedById = accessAttr.UpdatedById;
      this.UpdatedAt = accessAttr.UpdatedAt;
    }
  }

  public static async init(
    dbTransaction: any,
    CompanyAccessId?: string,
  ): Promise<CompanyFeedAccess> {
    try {
      if (CompanyAccessId) {
        const access = await CompanyFeedAccess._Repo.findByPk(
          CompanyAccessId,
          dbTransaction,
        );
        if (access) {
          return new CompanyFeedAccess(access as PriceCompanyFeedAccessAttr);
        } else {
          throw new ClassError(
            'CompanyFeedAccess',
            'CompanyFeedAccessErrMsg00',
            'Company Feed Access Not Found',
          );
        }
      }
      return new CompanyFeedAccess();
    } catch (error) {
      throw new ClassError(
        'CompanyFeedAccess',
        'CompanyFeedAccessErrMsg01',
        error.message,
      );
    }
  }

  public async save(dbTransaction: any) {
    try {
      const existingAccess = await CompanyFeedAccess._Repo.findOne({
        where: {
          CompanyCode: this.CompanyCode,
          FeedName: this.FeedName,
        },
        transaction: dbTransaction,
      });

      const payload: PriceCompanyFeedAccessAttr = {
        CompanyCode: this.CompanyCode,
        FeedName: this.FeedName,
        Status: this.Status,
        PricingLogic: this.PricingLogic,
        CreatedById: this.CreatedById,
        UpdatedById: this.UpdatedById,
      };

      if (existingAccess) {
        payload.CompanyAccessId = existingAccess.CompanyAccessId;
        const affectedRows = await CompanyFeedAccess._Repo.update(payload, {
          where: { CompanyAccessId: existingAccess.CompanyAccessId },
          transaction: dbTransaction,
        });

        if (affectedRows[0] === 0) {
          throw new Error('Failed to update company feed access');
        }

        this.CompanyAccessId = existingAccess.CompanyAccessId;
      } else {
        payload.CompanyAccessId = createId();
        const newAccess = await CompanyFeedAccess._Repo.create(payload, {
          transaction: dbTransaction,
        });
        this.CompanyAccessId = newAccess.CompanyAccessId;
        this.CreatedAt = newAccess.CreatedAt;
        this.UpdatedAt = newAccess.UpdatedAt;
      }
    } catch (error) {
      throw new ClassError(
        'CompanyFeedAccess',
        'CompanyFeedAccessErrMsg02',
        error.message,
      );
    }
  }

  public static async hasAccessToFeed(
    dbTransaction: any,
    feedName: NonSourceFeedName,
    companyCode: string,
  ): Promise<boolean> {
    try {
      // Check if there are any access rules for this feed
      const accessRules = await CompanyFeedAccess._Repo.findAll({
        where: {
          FeedName: feedName,
          Status: 'Active',
        },
        transaction: dbTransaction,
      });

      // If no access rules exist for this feed, it's available to all companies
      if (accessRules.length === 0) {
        return true;
      }

      // Check if the company is in the allowed list
      const hasAccess = accessRules.some(
        (rule: any) => rule.CompanyCode === companyCode,
      );
      return hasAccess;
    } catch (error) {
      // Fail open - allow access if there's an error
      return true;
    }
  }

  public static async getAccessibleFeeds(
    dbTransaction: any,
    companyCode: string,
  ): Promise<NonSourceFeedName[]> {
    try {
      // Get all active access rules for this company
      const accessRules = await CompanyFeedAccess._Repo.findAll({
        where: {
          CompanyCode: companyCode,
          Status: 'Active',
        },
        attributes: ['FeedName'],
        transaction: dbTransaction,
      });

      // Return only the feed names that have explicit access rules
      return accessRules.map((rule: any) => rule.FeedName as NonSourceFeedName);
    } catch (error) {
      // Return empty array if there's an error (fail closed)
      return [];
    }
  }

  public static async validateFeedAccess(
    dbTransaction: any,
    feedName: NonSourceFeedName,
    companyCode: string,
  ): Promise<void> {
    const hasAccess = await CompanyFeedAccess.hasAccessToFeed(
      dbTransaction,
      feedName,
      companyCode,
    );
    if (!hasAccess) {
      const allowedCompanies =
        await CompanyFeedAccess.getCompaniesWithAccessToFeed(
          dbTransaction,
          feedName,
        );
      throw new Error(
        `Access denied: Company '${companyCode}' does not have access to feed '${feedName}'. ` +
          `This feed is restricted to: ${allowedCompanies.join(', ')}`,
      );
    }
  }

  public static async getCompaniesWithAccessToFeed(
    dbTransaction: any,
    feedName: NonSourceFeedName,
  ): Promise<string[]> {
    try {
      const accessRules = await CompanyFeedAccess._Repo.findAll({
        where: {
          FeedName: feedName,
          Status: 'Active',
        },
        attributes: ['CompanyCode'],
        transaction: dbTransaction,
      });

      return accessRules.map((rule: any) => rule.CompanyCode);
    } catch (error) {
      return [];
    }
  }

  public static async getAllFeedAccessConfiguration(
    dbTransaction: any,
  ): Promise<Record<string, string[]>> {
    try {
      const accessRules = await CompanyFeedAccess._Repo.findAll({
        where: {
          Status: 'Active',
        },
        attributes: ['FeedName', 'CompanyCode'],
        transaction: dbTransaction,
      });

      const feedAccess: Record<string, string[]> = {};
      for (const rule of accessRules) {
        if (!feedAccess[rule.FeedName]) {
          feedAccess[rule.FeedName] = [];
        }
        feedAccess[rule.FeedName].push(rule.CompanyCode);
      }

      return feedAccess;
    } catch (error) {
      return {};
    }
  }

  public static async getPricingLogic(
    dbTransaction: any,
    feedName: NonSourceFeedName,
    companyCode: string,
  ): Promise<PricingLogic> {
    try {
      const accessRule = await CompanyFeedAccess._Repo.findOne({
        where: {
          FeedName: feedName,
          CompanyCode: companyCode,
          Status: 'Active',
        },
        transaction: dbTransaction,
      });

      if (!accessRule) {
        throw new Error(
          `No pricing logic configuration found for company '${companyCode}' and feed '${feedName}'`,
        );
      }

      return accessRule.PricingLogic;
    } catch (error) {
      throw new Error(`Failed to retrieve pricing logic: ${error.message}`);
    }
  }
}
