/**
 * Enhanced Access Control Generator
 *
 * Generates comprehensive access control systems including:
 * - RBAC (Role-Based Access Control)
 * - ABAC (Attribute-Based Access Control)
 * - Hybrid RBAC + ABAC models
 * - Policy engine for complex authorization rules
 * - Middleware for Express route protection
 * - React hooks for UI permission checks
 */

import { Generator, GeneratorContext, GenerationResult, GeneratedFile } from '../types';
import { AccessControl, Role as _Role, Permission as _Permission, Policy as _Policy } from '../../types/workflow';
import * as path from 'path';

export class EnhancedAccessControlGenerator implements Generator {
  id = 'enhanced-access-control';
  name = 'Enhanced Access Control Generator';
  description = 'Generates comprehensive access control system from enhanced schema';

  generate(context: GeneratorContext): GenerationResult {
    const { workflow, outputDirectory } = context;
    const files: GeneratedFile[] = [];

    if (!workflow.access_control) {
      return { files: [] };
    }

    const accessControl = workflow.access_control as AccessControl;

    // Find backend and frontend components
    const backendComponent = Object.entries(workflow.stack).find(([_, c]) => c.type === 'backend');
    const frontendComponent = Object.entries(workflow.stack).find(([_, c]) => c.type === 'frontend');

    // Generate backend access control
    if (backendComponent) {
      const [componentName] = backendComponent;
      files.push(...this.generateBackendAccessControl(componentName, accessControl, outputDirectory));
    }

    // Generate frontend access control
    if (frontendComponent) {
      const [componentName] = frontendComponent;
      files.push(...this.generateFrontendAccessControl(componentName, accessControl, outputDirectory));
    }

    return { files };
  }

  private generateBackendAccessControl(
    componentName: string,
    accessControl: AccessControl,
    outputDirectory: string
  ): GeneratedFile[] {
    const files: GeneratedFile[] = [];
    const baseDir = path.join(outputDirectory, componentName);

    // Generate role and permission definitions
    files.push({
      path: path.join(baseDir, 'src/types/access-control.ts'),
      content: this.generateAccessControlTypes(accessControl),
    });

    // Generate role definitions
    files.push({
      path: path.join(baseDir, 'src/config/roles.ts'),
      content: this.generateRolesConfig(accessControl),
    });

    // Generate authorization service based on model
    if (accessControl.model === 'rbac') {
      files.push({
        path: path.join(baseDir, 'src/services/rbac.ts'),
        content: this.generateRBACService(accessControl),
      });
    } else if (accessControl.model === 'abac') {
      files.push({
        path: path.join(baseDir, 'src/services/abac.ts'),
        content: this.generateABACService(accessControl),
      });
    } else if (accessControl.model === 'rbac_with_abac') {
      files.push({
        path: path.join(baseDir, 'src/services/rbac.ts'),
        content: this.generateRBACService(accessControl),
      });
      files.push({
        path: path.join(baseDir, 'src/services/abac.ts'),
        content: this.generateABACService(accessControl),
      });
      files.push({
        path: path.join(baseDir, 'src/services/authorization.ts'),
        content: this.generateHybridAuthService(accessControl),
      });
    }

    // Generate policy engine if policies are defined
    if (accessControl.policies && accessControl.policies.length > 0) {
      files.push({
        path: path.join(baseDir, 'src/services/policy-engine.ts'),
        content: this.generatePolicyEngine(accessControl),
      });
    }

    // Generate Express middleware
    files.push({
      path: path.join(baseDir, 'src/middleware/authorization.ts'),
      content: this.generateAuthorizationMiddleware(accessControl),
    });

    // Generate utility decorators
    files.push({
      path: path.join(baseDir, 'src/utils/auth-decorators.ts'),
      content: this.generateAuthDecorators(accessControl),
    });

    return files;
  }

  private generateFrontendAccessControl(
    componentName: string,
    accessControl: AccessControl,
    outputDirectory: string
  ): GeneratedFile[] {
    const files: GeneratedFile[] = [];
    const baseDir = path.join(outputDirectory, componentName);

    // Generate permission types
    files.push({
      path: path.join(baseDir, 'src/types/permissions.ts'),
      content: this.generatePermissionTypes(accessControl),
    });

    // Generate permission hooks
    files.push({
      path: path.join(baseDir, 'src/hooks/usePermissions.ts'),
      content: this.generatePermissionHooks(accessControl),
    });

    // Generate permission components
    files.push({
      path: path.join(baseDir, 'src/components/Can.tsx'),
      content: this.generateCanComponent(accessControl),
    });

    // Generate authorization context
    files.push({
      path: path.join(baseDir, 'src/providers/AuthorizationProvider.tsx'),
      content: this.generateAuthorizationProvider(accessControl),
    });

    return files;
  }

  private generateAccessControlTypes(accessControl: AccessControl): string {
    const roles = accessControl.roles || [];
    const roleNames = roles.map(r => r.name);

    return `/**
 * Access Control Types
 * Generated by Bangstack
 * Model: ${accessControl.model}
 */

export type RoleName = ${roleNames.length > 0 ? roleNames.map(r => `'${r}'`).join(' | ') : 'string'};

export interface Permission {
  resource: string;
  actions: string[];
  conditions?: Condition[];
}

export interface Condition {
  field: string;
  operator: 'equals' | 'contains' | 'gt' | 'lt' | 'gte' | 'lte';
  value: any;
}

export interface Role {
  name: RoleName;
  permissions: Permission[];
  description?: string;
}

export interface Policy {
  name: string;
  resource: string;
  action: string;
  rules: PolicyRule[];
}

export interface PolicyRule {
  role?: string;
  condition?: string;
  effect: 'allow' | 'deny';
}

export interface User {
  id: string;
  roles: RoleName[];
  attributes?: Record<string, any>;
}

export interface AuthorizationContext {
  user: User;
  resource: string;
  action: string;
  resourceAttributes?: Record<string, any>;
}
`;
  }

  private generateRolesConfig(accessControl: AccessControl): string {
    const roles = accessControl.roles || [];

    return `/**
 * Role Definitions
 * Generated by Bangstack
 */

import { Role } from '../types/access-control';

export const roles: Role[] = [
${roles.map(role => `  {
    name: '${role.name}',
    description: 'Role: ${role.name}',
    permissions: [
${role.permissions.map(perm => `      {
        resource: '${perm.resource}',
        actions: ${JSON.stringify(perm.actions)},${perm.conditions ? `\n        conditions: ${JSON.stringify(perm.conditions, null, 10).replace(/\n/g, '\n        ')},` : ''}
      },`).join('\n')}
    ],
  },`).join('\n')}
];

export function getRoleByName(name: string): Role | undefined {
  return roles.find(r => r.name === name);
}

export function getRolePermissions(roleName: string): Permission[] {
  const role = getRoleByName(roleName);
  return role?.permissions || [];
}
`;
  }

  private generateRBACService(_accessControl: AccessControl): string {
    return `/**
 * RBAC (Role-Based Access Control) Service
 * Generated by Bangstack
 */

import { roles, getRolePermissions } from '../config/roles';
import { User, Permission, AuthorizationContext } from '../types/access-control';

export class RBACService {
  /**
   * Check if user has permission to perform action on resource
   */
  can(user: User, resource: string, action: string): boolean {
    // Get all permissions from user's roles
    const permissions = this.getUserPermissions(user);

    // Check if any permission grants access
    return permissions.some(permission => {
      if (permission.resource !== resource && permission.resource !== '*') {
        return false;
      }

      return permission.actions.includes(action) || permission.actions.includes('*');
    });
  }

  /**
   * Get all permissions for a user
   */
  getUserPermissions(user: User): Permission[] {
    const permissions: Permission[] = [];

    for (const roleName of user.roles) {
      const rolePermissions = getRolePermissions(roleName);
      permissions.push(...rolePermissions);
    }

    return permissions;
  }

  /**
   * Check if user has any of the specified roles
   */
  hasRole(user: User, ...roleNames: string[]): boolean {
    return roleNames.some(roleName => user.roles.includes(roleName));
  }

  /**
   * Check if user has all of the specified roles
   */
  hasAllRoles(user: User, ...roleNames: string[]): boolean {
    return roleNames.every(roleName => user.roles.includes(roleName));
  }

  /**
   * Get all resources user can access
   */
  getAccessibleResources(user: User, action: string): string[] {
    const permissions = this.getUserPermissions(user);
    const resources = new Set<string>();

    for (const permission of permissions) {
      if (permission.actions.includes(action) || permission.actions.includes('*')) {
        resources.add(permission.resource);
      }
    }

    return Array.from(resources);
  }
}

export const rbacService = new RBACService();
`;
  }

  private generateABACService(_accessControl: AccessControl): string {
    return `/**
 * ABAC (Attribute-Based Access Control) Service
 * Generated by Bangstack
 */

import { User, Condition, AuthorizationContext } from '../types/access-control';

export class ABACSer vice {
  /**
   * Evaluate a single condition
   */
  evaluateCondition(
    condition: Condition,
    user: User,
    resourceAttributes?: Record<string, any>
  ): boolean {
    const contextValue = this.getContextValue(condition.field, user, resourceAttributes);

    switch (condition.operator) {
      case 'equals':
        return contextValue === condition.value;
      case 'contains':
        if (Array.isArray(contextValue)) {
          return contextValue.includes(condition.value);
        }
        if (typeof contextValue === 'string') {
          return contextValue.includes(condition.value);
        }
        return false;
      case 'gt':
        return contextValue > condition.value;
      case 'lt':
        return contextValue < condition.value;
      case 'gte':
        return contextValue >= condition.value;
      case 'lte':
        return contextValue <= condition.value;
      default:
        return false;
    }
  }

  /**
   * Evaluate multiple conditions (AND logic)
   */
  evaluateConditions(
    conditions: Condition[],
    user: User,
    resourceAttributes?: Record<string, any>
  ): boolean {
    return conditions.every(condition =>
      this.evaluateCondition(condition, user, resourceAttributes)
    );
  }

  /**
   * Get value from context based on field path
   */
  private getContextValue(
    field: string,
    user: User,
    resourceAttributes?: Record<string, any>
  ): any {
    // Support dot notation: user.attributes.department
    const parts = field.split('.');

    if (parts[0] === 'user') {
      let value: any = user;
      for (let i = 1; i < parts.length; i++) {
        value = value?.[parts[i]];
      }
      return value;
    }

    if (parts[0] === 'resource' && resourceAttributes) {
      let value: any = resourceAttributes;
      for (let i = 1; i < parts.length; i++) {
        value = value?.[parts[i]];
      }
      return value;
    }

    return undefined;
  }

  /**
   * Check authorization with context
   */
  authorize(context: AuthorizationContext, conditions: Condition[]): boolean {
    return this.evaluateConditions(conditions, context.user, context.resourceAttributes);
  }
}

export const abacService = new ABACService();
`;
  }

  private generateHybridAuthService(_accessControl: AccessControl): string {
    return `/**
 * Hybrid Authorization Service (RBAC + ABAC)
 * Generated by Bangstack
 */

import { rbacService } from './rbac';
import { abacService } from './abac';
import { User, Permission, AuthorizationContext } from '../types/access-control';
import { getRolePermissions } from '../config/roles';

export class AuthorizationService {
  /**
   * Check if user is authorized to perform action on resource
   * Combines RBAC and ABAC checks
   */
  can(
    user: User,
    resource: string,
    action: string,
    resourceAttributes?: Record<string, any>
  ): boolean {
    // First check RBAC permissions
    const hasRBACPermission = rbacService.can(user, resource, action);

    if (!hasRBACPermission) {
      return false;
    }

    // Then check ABAC conditions if present
    const permissions = this.getUserPermissionsForResource(user, resource, action);

    // If no conditions, RBAC permission is sufficient
    const permissionsWithConditions = permissions.filter(p => p.conditions && p.conditions.length > 0);
    if (permissionsWithConditions.length === 0) {
      return true;
    }

    // Evaluate ABAC conditions
    return permissionsWithConditions.some(permission => {
      if (!permission.conditions) return true;
      return abacService.evaluateConditions(permission.conditions, user, resourceAttributes);
    });
  }

  /**
   * Get user permissions for specific resource and action
   */
  private getUserPermissionsForResource(
    user: User,
    resource: string,
    action: string
  ): Permission[] {
    const allPermissions: Permission[] = [];

    for (const roleName of user.roles) {
      const rolePermissions = getRolePermissions(roleName);
      const matchingPermissions = rolePermissions.filter(
        p => (p.resource === resource || p.resource === '*') &&
             (p.actions.includes(action) || p.actions.includes('*'))
      );
      allPermissions.push(...matchingPermissions);
    }

    return allPermissions;
  }

  /**
   * Authorize with full context
   */
  authorize(context: AuthorizationContext): boolean {
    return this.can(
      context.user,
      context.resource,
      context.action,
      context.resourceAttributes
    );
  }
}

export const authorizationService = new AuthorizationService();
`;
  }

  private generatePolicyEngine(accessControl: AccessControl): string {
    const policies = accessControl.policies || [];

    return `/**
 * Policy Engine
 * Generated by Bangstack
 */

import { Policy, PolicyRule, User } from '../types/access-control';

const policies: Policy[] = [
${policies.map(policy => `  {
    name: '${policy.name}',
    resource: '${policy.resource}',
    action: '${policy.action}',
    rules: ${JSON.stringify(policy.rules, null, 6).replace(/\n/g, '\n    ')},
  },`).join('\n')}
];

export class PolicyEngine {
  /**
   * Evaluate a policy for a user
   */
  evaluatePolicy(policy: Policy, user: User): 'allow' | 'deny' | 'neutral' {
    for (const rule of policy.rules) {
      if (rule.role && user.roles.includes(rule.role)) {
        return rule.effect || 'allow';
      }

      // Evaluate condition-based rules
      if (rule.condition) {
        const conditionMet = this.evaluateConditionString(rule.condition, user);
        if (conditionMet) {
          return rule.effect || 'allow';
        }
      }
    }

    return 'neutral';
  }

  /**
   * Check if user can perform action on resource based on policies
   */
  can(user: User, resource: string, action: string): boolean {
    const applicablePolicies = policies.filter(
      p => p.resource === resource && p.action === action
    );

    if (applicablePolicies.length === 0) {
      return false;
    }

    // Check if any policy explicitly denies
    const hasDeny = applicablePolicies.some(
      policy => this.evaluatePolicy(policy, user) === 'deny'
    );

    if (hasDeny) {
      return false;
    }

    // Check if any policy allows
    return applicablePolicies.some(
      policy => this.evaluatePolicy(policy, user) === 'allow'
    );
  }

  /**
   * Evaluate condition string (simple implementation)
   */
  private evaluateConditionString(condition: string, user: User): boolean {
    // This is a simplified implementation
    // In production, you'd use a proper expression evaluator
    try {
      // eslint-disable-next-line no-eval
      return eval(condition.replace(/user./g, 'user.attributes.'));
    } catch {
      return false;
    }
  }
}

export const policyEngine = new PolicyEngine();
`;
  }

  private generateAuthorizationMiddleware(accessControl: AccessControl): string {
    const serviceImport = accessControl.model === 'rbac_with_abac'
      ? "import { authorizationService } from '../services/authorization';"
      : accessControl.model === 'abac'
      ? "import { abacService } from '../services/abac';"
      : "import { rbacService } from '../services/rbac';";

    return `/**
 * Authorization Middleware
 * Generated by Bangstack
 */

import { Request, Response, NextFunction } from 'express';
${serviceImport}
import { User } from '../types/access-control';

/**
 * Middleware to check if user can perform action on resource
 */
export function authorize(resource: string, action: string) {
  return async (req: Request, res: Response, next: NextFunction) => {
    const user = (req as any).user as User;

    if (!user) {
      return res.status(401).json({
        error: 'Unauthorized',
        message: 'Authentication required',
      });
    }

    const resourceAttributes = (req as any).resourceAttributes || {};
    ${this.generateAuthCheckForModel(accessControl.model)}

    if (!authorized) {
      return res.status(403).json({
        error: 'Forbidden',
        message: \`You do not have permission to \${action} \${resource}\`,
      });
    }

    next();
  };
}

/**
 * Middleware to require specific role(s)
 */
export function requireRole(...roleNames: string[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    const user = (req as any).user as User;

    if (!user) {
      return res.status(401).json({
        error: 'Unauthorized',
        message: 'Authentication required',
      });
    }

    const hasRole = roleNames.some(roleName => user.roles.includes(roleName));

    if (!hasRole) {
      return res.status(403).json({
        error: 'Forbidden',
        message: \`Required role: \${roleNames.join(' or ')}\`,
      });
    }

    next();
  };
}

/**
 * Middleware to require all specified roles
 */
export function requireAllRoles(...roleNames: string[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    const user = (req as any).user as User;

    if (!user) {
      return res.status(401).json({
        error: 'Unauthorized',
        message: 'Authentication required',
      });
    }

    const hasAllRoles = roleNames.every(roleName => user.roles.includes(roleName));

    if (!hasAllRoles) {
      return res.status(403).json({
        error: 'Forbidden',
        message: \`Required roles: \${roleNames.join(', ')}\`,
      });
    }

    next();
  };
}
`;
  }

  private generateAuthCheckForModel(model: string): string {
    switch (model) {
      case 'rbac':
        return `const authorized = rbacService.can(user, resource, action);`;
      case 'abac':
        return `const authorized = abacService.authorize({ user, resource, action, resourceAttributes }, []);`;
      case 'rbac_with_abac':
        return `const authorized = authorizationService.can(user, resource, action, resourceAttributes);`;
      default:
        return `const authorized = rbacService.can(user, resource, action);`;
    }
  }

  private generateAuthDecorators(_accessControl: AccessControl): string {
    return `/**
 * Authorization Decorators
 * Generated by Bangstack
 */

/**
 * Decorator to require authorization for a method
 */
export function Authorize(resource: string, action: string) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: any[]) {
      // This is a placeholder - actual implementation depends on your framework
      // You would extract user from request and perform authorization check
      console.log(\`Checking authorization: \${resource}:\${action}\`);
      return originalMethod.apply(this, args);
    };

    return descriptor;
  };
}

/**
 * Decorator to require specific role
 */
export function RequireRole(...roles: string[]) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: any[]) {
      console.log(\`Checking roles: \${roles.join(', ')}\`);
      return originalMethod.apply(this, args);
    };

    return descriptor;
  };
}
`;
  }

  private generatePermissionTypes(accessControl: AccessControl): string {
    const roles = accessControl.roles || [];
    const allResources = new Set<string>();
    const allActions = new Set<string>();

    roles.forEach(role => {
      role.permissions.forEach(perm => {
        allResources.add(perm.resource);
        perm.actions.forEach(action => allActions.add(action));
      });
    });

    return `/**
 * Permission Types
 * Generated by Bangstack
 */

export type Resource = ${allResources.size > 0 ? Array.from(allResources).map(r => `'${r}'`).join(' | ') : 'string'};
export type Action = ${allActions.size > 0 ? Array.from(allActions).map(a => `'${a}'`).join(' | ') : 'string'};

export interface PermissionCheck {
  resource: Resource;
  action: Action;
}
`;
  }

  private generatePermissionHooks(_accessControl: AccessControl): string {
    return `/**
 * Permission Hooks
 * Generated by Bangstack
 */

import { useContext } from 'react';
import { AuthorizationContext } from '../providers/AuthorizationProvider';
import { Resource, Action } from '../types/permissions';

export function useCan(resource: Resource, action: Action): boolean {
  const context = useContext(AuthorizationContext);

  if (!context) {
    console.warn('useCan must be used within AuthorizationProvider');
    return false;
  }

  return context.can(resource, action);
}

export function usePermissions() {
  const context = useContext(AuthorizationContext);

  if (!context) {
    console.warn('usePermissions must be used within AuthorizationProvider');
    return { permissions: [], can: () => false, hasRole: () => false };
  }

  return {
    permissions: context.permissions,
    can: context.can,
    hasRole: context.hasRole,
  };
}

export function useRole(...roleNames: string[]): boolean {
  const context = useContext(AuthorizationContext);

  if (!context) {
    console.warn('useRole must be used within AuthorizationProvider');
    return false;
  }

  return context.hasRole(...roleNames);
}
`;
  }

  private generateCanComponent(_accessControl: AccessControl): string {
    return `/**
 * Can Component - Conditional rendering based on permissions
 * Generated by Bangstack
 */

import React, { ReactNode } from 'react';
import { useCan } from '../hooks/usePermissions';
import { Resource, Action } from '../types/permissions';

interface CanProps {
  perform: Action;
  on: Resource;
  children: ReactNode;
  fallback?: ReactNode;
}

export const Can: React.FC<CanProps> = ({ perform, on, children, fallback = null }) => {
  const can = useCan(on, perform);

  return <>{can ? children : fallback}</>;
};

interface CannotProps {
  perform: Action;
  on: Resource;
  children: ReactNode;
  fallback?: ReactNode;
}

export const Cannot: React.FC<CannotProps> = ({ perform, on, children, fallback = null }) => {
  const can = useCan(on, perform);

  return <>{!can ? children : fallback}</>;
};
`;
  }

  private generateAuthorizationProvider(_accessControl: AccessControl): string {
    return `/**
 * Authorization Provider
 * Generated by Bangstack
 */

import React, { createContext, useEffect, useState, ReactNode } from 'react';
import { Resource, Action } from '../types/permissions';

interface Permission {
  resource: string;
  actions: string[];
}

interface AuthorizationContextType {
  permissions: Permission[];
  can: (resource: Resource, action: Action) => boolean;
  hasRole: (...roles: string[]) => boolean;
  loading: boolean;
}

export const AuthorizationContext = createContext<AuthorizationContextType | null>(null);

interface AuthorizationProviderProps {
  children: ReactNode;
}

export const AuthorizationProvider: React.FC<AuthorizationProviderProps> = ({ children }) => {
  const [permissions, setPermissions] = useState<Permission[]>([]);
  const [userRoles, setUserRoles] = useState<string[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Fetch user permissions from API
    async function fetchPermissions() {
      try {
        const response = await fetch('/api/auth/permissions');
        const data = await response.json();
        setPermissions(data.permissions || []);
        setUserRoles(data.roles || []);
        setLoading(false);
      } catch (error) {
        console.error('Error fetching permissions:', error);
        setLoading(false);
      }
    }

    fetchPermissions();
  }, []);

  const can = (resource: Resource, action: Action): boolean => {
    return permissions.some(
      permission =>
        (permission.resource === resource || permission.resource === '*') &&
        (permission.actions.includes(action) || permission.actions.includes('*'))
    );
  };

  const hasRole = (...roles: string[]): boolean => {
    return roles.some(role => userRoles.includes(role));
  };

  return (
    <AuthorizationContext.Provider value={{ permissions, can, hasRole, loading }}>
      {children}
    </AuthorizationContext.Provider>
  );
};
`;
  }
}
