import SwaggerParser from "@apidevtools/swagger-parser";
import { AppConfig, ResourceConfig, ResourceField, FieldType } from "../types/config";
import { configuration } from "../configuration";

/**
 * Maps OpenAPI property types to our internal FieldType
 */
function mapOpenApiType(prop: any): FieldType {
  const type = prop.type;
  const format = prop.format;

  if (format === "date-time") return "datetime";
  if (format === "date") return "date";
  if (prop.enum) return "enum";
  if (
    type === "string" &&
    (prop.description?.toLowerCase().includes("image") ||
      prop.name?.toLowerCase().includes("icon"))
  )
    return "image";

  switch (type) {
    case "integer":
    case "number":
      return "number";
    case "boolean":
      return "boolean";
    case "object":
      return "object";
    case "array":
      return "array";
    default:
      return "string";
  }
}

/**
 * Recursively converts OpenAPI schemas to ResourceField map
 */
function parseSchemaFields(
  schema: any,
  resourceName: string,
  allResources: string[]
): Record<string, ResourceField> {
  const fields: Record<string, ResourceField> = {};
  const properties = schema.properties || {};
  const required = schema.required || [];
  const overrides = configuration[resourceName]?.fields || {};

  for (const [key, prop] of Object.entries(properties) as any) {
    const type = mapOpenApiType(prop);
    const override = overrides[key];

    console.log("key", key, "type", type, "prop", prop, "override", override);
    if (key !== "id" && override?.display !== false) {
      fields[key] = {
        type,
        label:
          prop.title ||
          key.charAt(0).toUpperCase() + key.slice(1).replace(/_/g, " "),
        required: required.includes(key),
        options: prop.enum,
        readOnly:
          prop.readOnly ||
          key === "created_at" ||
          key === "updated_at",
        ...override,
      };
    } else continue;

    // Schema-based Relation Detection
    // If it's an object/string and matches a resource name, it might be a relation
    const potentialRelation = allResources.find(
      (res) =>
        key === res ||
        key === `${res}_id` ||
        prop.title?.toLowerCase() === res ||
        prop["x-resource"] === res
    );

    if (potentialRelation) {
      if (type === "string" || (type === "object" && prop.properties?.id)) {
        fields[key].relation = potentialRelation;
      }
    }

    if (fields[key].type === "object" && prop.properties) {
      fields[key].schema = parseSchemaFields(prop, resourceName, allResources);
    }
  }

  return fields;
}

/**
 * Scans paths to identify resources and their basic configuration
 */
export async function loadConfigFromOpenApi(baseUrl: string): Promise<AppConfig> {
  // 1. Parse and dereference the spec (handles all $ref)
  const api = await SwaggerParser.dereference(
    new URL("/openapi.json", baseUrl).href
  );

  const resources: ResourceConfig[] = [];
  const paths = api.paths || {};

  // Group paths by base resource name (e.g., /expenses, /expenses/{id} -> expenses)
  const resourcePaths: Record<string, any> = {};

  for (const path of Object.keys(paths)) {
    const base = path.split("/")[1];
    if (!base) continue;

    if (!resourcePaths[base]) resourcePaths[base] = { path, methods: [] };
    const methods = Object.keys(paths[path] || {});
    resourcePaths[base].methods.push(...methods);

    // We prefer the plural GET path for schema extraction
    if (!path.includes("{") && paths[path]?.get?.responses?.["200"]) {
      resourcePaths[base].listPath = path;
    }
  }

  const allResourceNames = Object.keys(resourcePaths);

  // Generate ResourceConfig for each identified base path
  for (const [name, info] of Object.entries(resourcePaths)) {
    const listPath = info.listPath || `/${name}`;
    const listOp = paths[listPath]?.get;
    if (!listOp) continue;

    // Use common naming conventions or metadata from the spec
    const label = name.charAt(0).toUpperCase() + name.slice(1, -1); // naive singularization
    const pluralLabel = name.charAt(0).toUpperCase() + name.slice(1);

    // Extract schema from the 200 response of the list endpoint
    let schema: any = null;
    const responseSchema =
      listOp.responses?.["200"]?.content?.["application/json"]?.schema;

    if (responseSchema?.type === "array" && responseSchema.items) {
      schema = responseSchema.items;
    } else {
      schema = responseSchema;
    }

    if (schema) {
      resources.push({
        name,
        label: schema.title || label,
        pluralLabel: pluralLabel,
        endpoint: listPath,
        primaryKey: "id", // assume 'id' as default or look for 'required' + 'unique'
        fields: parseSchemaFields(schema, name, allResourceNames),
      });
    }
  }

  return {
    baseUrl:
      import.meta.env.VITE_API_BASE_URL || (api.servers?.[0]?.url ?? ""),
    authBaseUrl: import.meta.env.VITE_AUTH_BASE_URL || "",
    resources,
  };
}
