import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { CrmUserFieldType } from '@prisma/client';
import { PrismaService } from '../../../prisma/prisma.service';
import { CreateFieldDto } from '../dto/create-field.dto';
import { UpdateFieldDto } from '../dto/update-field.dto';

@Injectable()
export class UserFieldsService {
  constructor(private readonly prisma: PrismaService) {}

  /** Get all fields for entity type, scoped to club */
  async findByEntity(
    clubId: string | null,
    entityType: string,
    userRoles?: string[],
  ): Promise<any[]> {
    const fields = await this.prisma.crmUserField.findMany({
      where: { clubId, entityType, isActive: true },
      orderBy: { position: 'asc' },
    });

    // Filter out hidden fields for the user's roles
    if (userRoles && userRoles.length > 0) {
      return fields.filter((field) => {
        if (!field.roleAccess) return true;
        const access = field.roleAccess as Record<string, string>;
        // Field visible if any of user's roles has non-hidden access
        return userRoles.some((role) => {
          const level = access[role];
          return !level || level !== 'hidden';
        });
      });
    }

    return fields;
  }

  async getById(id: string): Promise<any> {
    const field = await this.prisma.crmUserField.findUnique({ where: { id } });
    if (!field) throw new NotFoundException(`Field ${id} not found`);
    return field;
  }

  async create(clubId: string | null, dto: CreateFieldDto): Promise<any> {
    // Check unique fieldName per club+entity
    const existing = await this.prisma.crmUserField.findUnique({
      where: {
        clubId_entityType_fieldName: {
          clubId: clubId ?? '',
          entityType: dto.entityType,
          fieldName: dto.fieldName,
        },
      },
    });
    if (existing) {
      throw new BadRequestException(
        `Field "${dto.fieldName}" already exists for ${dto.entityType}`,
      );
    }

    // Get next position
    const maxPos = await this.prisma.crmUserField.aggregate({
      where: { clubId, entityType: dto.entityType },
      _max: { position: true },
    });

    return this.prisma.crmUserField.create({
      data: {
        clubId,
        entityType: dto.entityType,
        name: dto.name,
        fieldName: dto.fieldName,
        type: dto.type,
        listOptions: dto.listOptions ?? undefined,
        isRequired: dto.isRequired ?? false,
        isMultiple: dto.isMultiple ?? false,
        showToTrainer: dto.showToTrainer ?? false,
        roleAccess: dto.roleAccess ?? undefined,
        defaultValue: dto.defaultValue,
        description: dto.description,
        position: (maxPos._max.position ?? 0) + 1,
      },
    });
  }

  async update(id: string, dto: UpdateFieldDto): Promise<any> {
    await this.getById(id);
    return this.prisma.crmUserField.update({
      where: { id },
      data: {
        ...(dto.name !== undefined ? { name: dto.name } : {}),
        ...(dto.type !== undefined ? { type: dto.type } : {}),
        ...(dto.listOptions !== undefined ? { listOptions: dto.listOptions } : {}),
        ...(dto.isRequired !== undefined ? { isRequired: dto.isRequired } : {}),
        ...(dto.isMultiple !== undefined ? { isMultiple: dto.isMultiple } : {}),
        ...(dto.showToTrainer !== undefined ? { showToTrainer: dto.showToTrainer } : {}),
        ...(dto.roleAccess !== undefined ? { roleAccess: dto.roleAccess } : {}),
        ...(dto.defaultValue !== undefined ? { defaultValue: dto.defaultValue } : {}),
        ...(dto.description !== undefined ? { description: dto.description } : {}),
      },
    });
  }

  async deactivate(id: string): Promise<any> {
    await this.getById(id);
    return this.prisma.crmUserField.update({
      where: { id },
      data: { isActive: false },
    });
  }

  async reorder(fieldIds: string[]): Promise<void> {
    await this.prisma.$transaction(
      fieldIds.map((id, index) =>
        this.prisma.crmUserField.update({
          where: { id },
          data: { position: index },
        }),
      ),
    );
  }

  // ------------------------------------------------------------------
  // Values
  // ------------------------------------------------------------------

  /** Get custom field values for an entity */
  async getValuesForEntity(entityId: string, entityType: string): Promise<Record<string, any>> {
    const values = await this.prisma.crmUserFieldValue.findMany({
      where: { entityId, entityType },
      include: { field: true },
    });

    const result: Record<string, any> = {};
    for (const v of values) {
      result[v.field.fieldName] = {
        fieldId: v.fieldId,
        name: v.field.name,
        type: v.field.type,
        value: this.deserializeValue(v.value, v.field.type),
        raw: v.value,
      };
    }
    return result;
  }

  /** Set custom field values for an entity (upsert) */
  async setValuesForEntity(
    entityId: string,
    entityType: string,
    clubId: string | null,
    values: Record<string, any>,
  ): Promise<void> {
    const fields = await this.findByEntity(clubId, entityType);
    const fieldMap = new Map(fields.map((f) => [f.fieldName, f]));

    const ops: any[] = [];
    for (const [fieldName, rawValue] of Object.entries(values)) {
      const field = fieldMap.get(fieldName);
      if (!field) continue;

      const serialized = this.serializeValue(rawValue, field.type);
      if (serialized === null && field.isRequired) {
        throw new BadRequestException(`Field "${field.name}" is required`);
      }

      ops.push(
        this.prisma.crmUserFieldValue.upsert({
          where: {
            fieldId_entityId: { fieldId: field.id, entityId },
          },
          create: {
            fieldId: field.id,
            entityId,
            entityType,
            value: serialized ?? '',
          },
          update: { value: serialized ?? '' },
        }),
      );
    }

    if (ops.length > 0) {
      await this.prisma.$transaction(ops);
    }
  }

  /** Validate values against field definitions */
  async validateValues(
    clubId: string | null,
    entityType: string,
    values: Record<string, any>,
  ): Promise<string[]> {
    const fields = await this.findByEntity(clubId, entityType);
    const errors: string[] = [];

    for (const field of fields) {
      const value = values[field.fieldName];

      if (field.isRequired && (value === undefined || value === null || value === '')) {
        errors.push(`Поле "${field.name}" обязательно`);
        continue;
      }

      if (value === undefined || value === null) continue;

      switch (field.type as CrmUserFieldType) {
        case 'INTEGER':
          if (!Number.isInteger(Number(value))) {
            errors.push(`Поле "${field.name}" должно быть целым числом`);
          }
          break;
        case 'FLOAT':
          if (isNaN(Number(value))) {
            errors.push(`Поле "${field.name}" должно быть числом`);
          }
          break;
        case 'BOOLEAN':
          if (typeof value !== 'boolean' && value !== 'true' && value !== 'false') {
            errors.push(`Поле "${field.name}" должно быть true/false`);
          }
          break;
        case 'LIST': {
          const options = (field.listOptions as string[]) || [];
          const vals = Array.isArray(value) ? value : [value];
          for (const v of vals) {
            if (!options.includes(String(v))) {
              errors.push(`Поле "${field.name}": значение "${v}" не найдено в списке`);
            }
          }
          break;
        }
        case 'DATE':
        case 'DATETIME':
          if (isNaN(Date.parse(String(value)))) {
            errors.push(`Поле "${field.name}" должно быть валидной датой`);
          }
          break;
      }
    }

    return errors;
  }

  // ------------------------------------------------------------------
  // Serialization
  // ------------------------------------------------------------------

  private serializeValue(value: any, type: CrmUserFieldType): string | null {
    if (value === null || value === undefined) return null;
    if (type === 'BOOLEAN') return value ? 'true' : 'false';
    if (type === 'LIST' && Array.isArray(value)) return JSON.stringify(value);
    return String(value);
  }

  private deserializeValue(raw: string, type: CrmUserFieldType): any {
    switch (type) {
      case 'INTEGER':
        return parseInt(raw, 10);
      case 'FLOAT':
        return parseFloat(raw);
      case 'BOOLEAN':
        return raw === 'true';
      case 'LIST':
        try {
          return JSON.parse(raw);
        } catch {
          return raw;
        }
      default:
        return raw;
    }
  }
}
