import type { Prisma, PrismaClient } from "@prisma/client";
import {
  ExperienceFindingStatus,
  ExperienceSignalStatus,
  ExperienceTemplateState,
  ExperienceScoreAggregationMode,
} from "@prisma/client";
import { prisma } from "@/lib/prisma";
import {
  EXPERIENCE_BASE_TEMPLATE_CATEGORIES,
  EXPERIENCE_BASE_TEMPLATE_NAME,
  EXPERIENCE_BASELINE_LOCATIONS,
  EXPERIENCE_DEFAULT_POLICY,
} from "@/lib/experienciometro/baseline";
import {
  aggregateLocationScore,
  parseRecentWeightsCsv,
  signalFromScore,
  stringifyRecentWeights,
  trendDirectionFromDelta,
} from "@/lib/experienciometro/scoring";
import type {
  ExperienceCategoryScoreRow,
  ExperienceEvidenceDTO,
  ExperienceEvaluationDTO,
  ExperienceFindingDTO,
  ExperienceLocationSummary,
  ExperiencePolicyDTO,
  ExperienceTemplateDTO,
} from "@/lib/experienciometro/types";

type PrismaLike = PrismaClient | Prisma.TransactionClient;

const EXPERIENCE_LEGACY_LOCATION_CODE_MAP: Record<string, string> = {
  "cdmx-01": "cdmx-roma-norte",
  "mty-01": "mty-tampiquito",
  "mty-02": "mty-cordillera",
  "mty-03": "mty-armida",
  "mty-04": "mty-centro",
  "mty-05": "mty-innovasport",
  "mty-06": "mty-terminal-a",
  "mty-07": "mty-terminal-b",
  "mty-08": "mty-park-point",
};

export function canAccessExperienciometro(role: string | undefined): boolean {
  return role === "owner" || role === "leader";
}

export function canManageExperienciometro(role: string | undefined): boolean {
  return canAccessExperienciometro(role);
}

export function canConfigureExperienciometro(role: string | undefined): boolean {
  return role === "owner";
}

function toIso(value: Date | null): string | null {
  return value ? value.toISOString() : null;
}

export function mapExperiencePolicy(policy: {
  id: string;
  name: string;
  isActive: boolean;
  aggregationMode: ExperienceScoreAggregationMode;
  recentWindow: number;
  recentWeightsCsv: string;
  greenThreshold: number;
  yellowThreshold: number;
  updatedAt: Date;
}): ExperiencePolicyDTO {
  return {
    id: policy.id,
    name: policy.name,
    isActive: policy.isActive,
    aggregationMode: policy.aggregationMode,
    recentWindow: policy.recentWindow,
    recentWeights: parseRecentWeightsCsv(policy.recentWeightsCsv),
    greenThreshold: policy.greenThreshold,
    yellowThreshold: policy.yellowThreshold,
    updatedAt: policy.updatedAt.toISOString(),
  };
}

export function mapExperienceTemplate(template: {
  id: string;
  name: string;
  version: number;
  state: ExperienceTemplateState;
  isDefault: boolean;
  publishedAt: Date | null;
  updatedAt: Date;
  categories: Array<{
    id: string;
    key: string;
    name: string;
    weight: number;
    sortOrder: number;
    items: Array<{
      id: string;
      key: string;
      label: string;
      weight: number;
      sortOrder: number;
      allowsComment: boolean;
      requiresObservation: boolean;
      allowsEvidence: boolean;
      isActive: boolean;
    }>;
  }>;
}): ExperienceTemplateDTO {
  return {
    id: template.id,
    name: template.name,
    version: template.version,
    state: template.state,
    isDefault: template.isDefault,
    publishedAt: toIso(template.publishedAt),
    updatedAt: template.updatedAt.toISOString(),
    categories: template.categories
      .slice()
      .sort((a, b) => a.sortOrder - b.sortOrder)
      .map((category) => ({
        id: category.id,
        key: category.key,
        name: category.name,
        weight: category.weight,
        sortOrder: category.sortOrder,
        items: category.items
          .slice()
          .sort((a, b) => a.sortOrder - b.sortOrder)
          .map((item) => ({
            id: item.id,
            key: item.key,
            label: item.label,
            weight: item.weight,
            sortOrder: item.sortOrder,
            allowsComment: item.allowsComment,
            requiresObservation: item.requiresObservation,
            allowsEvidence: item.allowsEvidence,
            isActive: item.isActive,
          })),
      })),
  };
}

export function mapExperienceEvaluation(evaluation: {
  id: string;
  locationId: string;
  templateId: string;
  evaluatedAt: Date;
  totalScore: number;
  signal: ExperienceSignalStatus;
  generalObservations: string;
  strengths: string;
  improvementAreas: string;
  createdBy: { name: string | null; email: string } | null;
  responses: Array<{
    id: string;
    categoryId: string;
    itemId: string;
    score: number;
    scorePct: number;
    comment: string | null;
    observation: string | null;
    hasObservation: boolean;
    categoryLabelSnapshot: string;
    itemLabelSnapshot: string;
  }>;
}): ExperienceEvaluationDTO {
  return {
    id: evaluation.id,
    locationId: evaluation.locationId,
    templateId: evaluation.templateId,
    createdByName: evaluation.createdBy ? evaluation.createdBy.name?.trim() || evaluation.createdBy.email : null,
    evaluatedAt: evaluation.evaluatedAt.toISOString(),
    totalScore: evaluation.totalScore,
    signal: evaluation.signal,
    generalObservations: evaluation.generalObservations,
    strengths: evaluation.strengths,
    improvementAreas: evaluation.improvementAreas,
    responses: evaluation.responses.map((response) => ({
      id: response.id,
      categoryId: response.categoryId,
      itemId: response.itemId,
      score: response.score,
      scorePct: response.scorePct,
      comment: response.comment,
      observation: response.observation,
      hasObservation: response.hasObservation,
      categoryLabelSnapshot: response.categoryLabelSnapshot,
      itemLabelSnapshot: response.itemLabelSnapshot,
    })),
  };
}

export function mapExperienceFinding(finding: {
  id: string;
  locationId: string;
  evaluationId: string | null;
  title: string;
  description: string;
  categoryKey: string | null;
  categoryLabel: string | null;
  priority: "low" | "medium" | "high" | "critical";
  status: "open" | "in_progress" | "resolved" | "closed";
  dueDate: Date | null;
  resolvedAt: Date | null;
  createdAt: Date;
  updatedAt: Date;
  responsibleUserId: string | null;
  responsibleUser: { name: string | null; email: string } | null;
  createdBy: { name: string | null; email: string } | null;
}): ExperienceFindingDTO {
  return {
    id: finding.id,
    locationId: finding.locationId,
    evaluationId: finding.evaluationId,
    title: finding.title,
    description: finding.description,
    categoryKey: finding.categoryKey,
    categoryLabel: finding.categoryLabel,
    priority: finding.priority,
    status: finding.status,
    dueDate: toIso(finding.dueDate),
    resolvedAt: toIso(finding.resolvedAt),
    responsibleUserId: finding.responsibleUserId,
    responsibleName: finding.responsibleUser ? finding.responsibleUser.name?.trim() || finding.responsibleUser.email : null,
    createdByName: finding.createdBy ? finding.createdBy.name?.trim() || finding.createdBy.email : null,
    createdAt: finding.createdAt.toISOString(),
    updatedAt: finding.updatedAt.toISOString(),
  };
}

export function mapExperienceEvidence(evidence: {
  id: string;
  locationId: string;
  evaluationId: string | null;
  findingId: string | null;
  responseId: string | null;
  kind: "photo" | "document" | "link";
  title: string;
  note: string | null;
  url: string | null;
  storagePath: string | null;
  mimeType: string | null;
  sizeBytes: number | null;
  createdAt: Date;
  updatedAt: Date;
  uploadedBy: { name: string | null; email: string } | null;
}): ExperienceEvidenceDTO {
  return {
    id: evidence.id,
    locationId: evidence.locationId,
    evaluationId: evidence.evaluationId,
    findingId: evidence.findingId,
    responseId: evidence.responseId,
    kind: evidence.kind,
    title: evidence.title,
    note: evidence.note,
    url: evidence.url,
    hasFile: Boolean(evidence.storagePath),
    mimeType: evidence.mimeType,
    sizeBytes: evidence.sizeBytes,
    createdAt: evidence.createdAt.toISOString(),
    updatedAt: evidence.updatedAt.toISOString(),
    uploaderName: evidence.uploadedBy ? evidence.uploadedBy.name?.trim() || evidence.uploadedBy.email : null,
  };
}

export function mapLocationSummary(location: {
  id: string;
  code: string;
  name: string;
  city: string | null;
  managerUser: { name: string | null; email: string } | null;
  metrics: {
    currentScore: number;
    signal: "green" | "yellow" | "red";
    trendDelta: number;
    trendDirection: "up" | "down" | "flat";
    lastEvaluationAt: Date | null;
    totalEvaluations: number;
    openFindings: number;
  } | null;
}): ExperienceLocationSummary {
  return {
    id: location.id,
    code: location.code,
    name: location.name,
    city: location.city,
    managerName: location.managerUser ? location.managerUser.name?.trim() || location.managerUser.email : null,
    score: location.metrics?.currentScore ?? 0,
    signal: location.metrics?.signal ?? "yellow",
    trendDelta: location.metrics?.trendDelta ?? 0,
    trendDirection: location.metrics?.trendDirection ?? "flat",
    lastEvaluationAt: toIso(location.metrics?.lastEvaluationAt ?? null),
    totalEvaluations: location.metrics?.totalEvaluations ?? 0,
    openFindings: location.metrics?.openFindings ?? 0,
  };
}

export function buildCategoryBreakdown(evaluations: Array<{
  responses: Array<{
    categoryId: string;
    categoryLabelSnapshot: string;
    categoryWeightSnapshot: number;
    score: number;
    scorePct: number;
  }>;
}>): ExperienceCategoryScoreRow[] {
  const buckets = new Map<
    string,
    {
      categoryId: string;
      categoryName: string;
      categoryKey: string;
      totalScoreRaw: number;
      totalScorePct: number;
      responses: number;
    }
  >();

  for (const evaluation of evaluations) {
    for (const response of evaluation.responses) {
      const key = response.categoryId;
      const existing = buckets.get(key) ?? {
        categoryId: response.categoryId,
        categoryName: response.categoryLabelSnapshot,
        categoryKey: response.categoryLabelSnapshot
          .toLowerCase()
          .normalize("NFD")
          .replace(/[\u0300-\u036f]/g, "")
          .replace(/[^a-z0-9]+/g, "_")
          .replace(/^_+|_+$/g, ""),
        totalScoreRaw: 0,
        totalScorePct: 0,
        responses: 0,
      };

      existing.totalScoreRaw += response.score;
      existing.totalScorePct += response.scorePct;
      existing.responses += 1;
      buckets.set(key, existing);
    }
  }

  return Array.from(buckets.values())
    .map((bucket) => ({
      categoryId: bucket.categoryId,
      categoryKey: bucket.categoryKey,
      categoryName: bucket.categoryName,
      avgScoreRaw: bucket.responses > 0 ? Math.round((bucket.totalScoreRaw / bucket.responses) * 10) / 10 : 0,
      avgScorePct: bucket.responses > 0 ? Math.round((bucket.totalScorePct / bucket.responses) * 10) / 10 : 0,
      responses: bucket.responses,
    }))
    .sort((a, b) => b.avgScorePct - a.avgScorePct);
}

export async function ensureExperienceBaseline(client: PrismaLike, userId?: string): Promise<void> {
  for (const [legacyCode, nextCode] of Object.entries(EXPERIENCE_LEGACY_LOCATION_CODE_MAP)) {
    const legacy = await client.location.findUnique({
      where: { code: legacyCode },
      select: { id: true },
    });
    if (!legacy) {
      continue;
    }

    const nextLocationMeta = EXPERIENCE_BASELINE_LOCATIONS.find((location) => location.code === nextCode);
    if (!nextLocationMeta) {
      continue;
    }

    const target = await client.location.findUnique({
      where: { code: nextCode },
      select: { id: true },
    });

    if (target) {
      await client.location.update({
        where: { id: legacy.id },
        data: {
          name: nextLocationMeta.name,
          city: nextLocationMeta.city,
          isActive: false,
        },
      });
      continue;
    }

    await client.location.update({
      where: { id: legacy.id },
      data: {
        code: nextLocationMeta.code,
        name: nextLocationMeta.name,
        city: nextLocationMeta.city,
        isActive: true,
      },
    });
  }

  for (const location of EXPERIENCE_BASELINE_LOCATIONS) {
    await client.location.upsert({
      where: { code: location.code },
      update: {
        name: location.name,
        city: location.city,
        isActive: true,
      },
      create: {
        code: location.code,
        name: location.name,
        city: location.city,
        isActive: true,
      },
    });
  }

  const existingPublished = await client.experienceTemplate.findFirst({
    where: {
      state: ExperienceTemplateState.published,
      isDefault: true,
    },
    select: { id: true },
  });

  if (!existingPublished) {
    const latestVersion = await client.experienceTemplate.aggregate({
      _max: { version: true },
    });

    await client.experienceTemplate.create({
      data: {
        name: EXPERIENCE_BASE_TEMPLATE_NAME,
        version: (latestVersion._max.version ?? 0) + 1,
        state: ExperienceTemplateState.published,
        isDefault: true,
        createdById: userId ?? null,
        publishedAt: new Date(),
        categories: {
          create: EXPERIENCE_BASE_TEMPLATE_CATEGORIES.map((category, categoryIndex) => ({
            key: category.key,
            name: category.name,
            weight: category.weight ?? 1,
            sortOrder: categoryIndex,
            items: {
              create: category.items.map((item, itemIndex) => ({
                key: item.key,
                label: item.label,
                weight: item.weight ?? 1,
                sortOrder: itemIndex,
                allowsComment: item.allowsComment ?? true,
                requiresObservation: item.requiresObservation ?? false,
                allowsEvidence: item.allowsEvidence ?? true,
                isActive: true,
              })),
            },
          })),
        },
      },
    });
  }

  const activePolicy = await client.experienceScoringPolicy.findFirst({
    where: { isActive: true },
    select: { id: true },
  });

  if (!activePolicy) {
    await client.experienceScoringPolicy.create({
      data: {
        name: EXPERIENCE_DEFAULT_POLICY.name,
        isActive: true,
        aggregationMode: EXPERIENCE_DEFAULT_POLICY.aggregationMode,
        recentWindow: EXPERIENCE_DEFAULT_POLICY.recentWindow,
        recentWeightsCsv: stringifyRecentWeights(EXPERIENCE_DEFAULT_POLICY.recentWeights),
        greenThreshold: EXPERIENCE_DEFAULT_POLICY.greenThreshold,
        yellowThreshold: EXPERIENCE_DEFAULT_POLICY.yellowThreshold,
        createdById: userId ?? null,
      },
    });
  }

  const locations = await client.location.findMany({
    where: { isActive: true },
    select: { id: true },
  });

  for (const location of locations) {
    await client.experienceLocationMetric.upsert({
      where: { locationId: location.id },
      update: {},
      create: {
        locationId: location.id,
        currentScore: 0,
        previousScore: 0,
        trendDelta: 0,
        trendDirection: "flat",
        signal: "yellow",
        totalEvaluations: 0,
        openFindings: 0,
      },
    });
  }
}

export async function getActiveExperiencePolicy(client: PrismaLike = prisma) {
  const policy = await client.experienceScoringPolicy.findFirst({
    where: { isActive: true },
    orderBy: [{ updatedAt: "desc" }, { createdAt: "desc" }],
  });

  if (policy) {
    return policy;
  }

  await ensureExperienceBaseline(client);

  return client.experienceScoringPolicy.findFirstOrThrow({
    where: { isActive: true },
    orderBy: [{ updatedAt: "desc" }, { createdAt: "desc" }],
  });
}

export async function getPublishedExperienceTemplate(client: PrismaLike = prisma) {
  const template = await client.experienceTemplate.findFirst({
    where: {
      state: ExperienceTemplateState.published,
    },
    include: {
      categories: {
        include: {
          items: {
            orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
          },
        },
        orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
      },
    },
    orderBy: [{ isDefault: "desc" }, { publishedAt: "desc" }, { updatedAt: "desc" }],
  });

  if (template) {
    return template;
  }

  await ensureExperienceBaseline(client);

  return client.experienceTemplate.findFirstOrThrow({
    where: {
      state: ExperienceTemplateState.published,
    },
    include: {
      categories: {
        include: {
          items: {
            orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
          },
        },
        orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
      },
    },
    orderBy: [{ isDefault: "desc" }, { publishedAt: "desc" }, { updatedAt: "desc" }],
  });
}

export async function recalculateLocationMetrics(client: PrismaLike, locationId: string) {
  const [policy, evaluations, openFindings] = await Promise.all([
    getActiveExperiencePolicy(client),
    client.experienceEvaluation.findMany({
      where: { locationId },
      orderBy: [{ evaluatedAt: "desc" }, { createdAt: "desc" }],
      select: {
        id: true,
        evaluatedAt: true,
        totalScore: true,
      },
    }),
    client.experienceFinding.count({
      where: {
        locationId,
        status: {
          in: [ExperienceFindingStatus.open, ExperienceFindingStatus.in_progress],
        },
      },
    }),
  ]);

  const recentWeights = parseRecentWeightsCsv(policy.recentWeightsCsv);
  const scoresDesc = evaluations.map((evaluation) => evaluation.totalScore);

  const currentScore = aggregateLocationScore({
    scoresDesc,
    aggregationMode: policy.aggregationMode,
    recentWindow: policy.recentWindow,
    recentWeights,
  });

  const previousScore = aggregateLocationScore({
    scoresDesc: scoresDesc.slice(1),
    aggregationMode: policy.aggregationMode,
    recentWindow: policy.recentWindow,
    recentWeights,
  });

  const trendDelta = Math.round((currentScore - previousScore) * 10) / 10;
  const trendDirection = trendDirectionFromDelta(trendDelta);
  const signal = signalFromScore(currentScore, {
    greenThreshold: policy.greenThreshold,
    yellowThreshold: policy.yellowThreshold,
  });

  const latest = evaluations[0] ?? null;

  return client.experienceLocationMetric.upsert({
    where: { locationId },
    update: {
      currentScore,
      previousScore,
      trendDelta,
      trendDirection,
      signal,
      totalEvaluations: evaluations.length,
      openFindings,
      lastEvaluationId: latest?.id ?? null,
      lastEvaluationAt: latest?.evaluatedAt ?? null,
    },
    create: {
      locationId,
      currentScore,
      previousScore,
      trendDelta,
      trendDirection,
      signal,
      totalEvaluations: evaluations.length,
      openFindings,
      lastEvaluationId: latest?.id ?? null,
      lastEvaluationAt: latest?.evaluatedAt ?? null,
    },
  });
}
