package main

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	appErrors "github.com/nexuscrm/backend/pkg/errors"
)

type APIContract struct {
	Definitions []StructDef `json:"definitions"`
}

type StructDef struct {
	Name        string     `json:"name"`
	Description string     `json:"description"`
	Type        string     `json:"type"` // "struct"
	Fields      []FieldDef `json:"fields"`
}

type FieldDef struct {
	Name        string `json:"name"`
	Type        string `json:"type"` // "string", "integer", "boolean", "array<T>", "map<string,any>"
	Required    bool   `json:"required"`
	Description string `json:"description"`
}

func generateAPIDTOs(rootPath string) error {
	contractsPath := filepath.Join(rootPath, "backend/internal/bootstrap/api_contracts.json")

	// Read Contracts
	content, err := os.ReadFile(contractsPath)
	if err != nil {
		if os.IsNotExist(err) {
			fmt.Println("⚠️  No api_contracts.json found, skipping DTO generation")
			return nil
		}
		return appErrors.NewInternalError("failed to read api_contracts.json", err)
	}

	var contract APIContract
	if err := json.Unmarshal(content, &contract); err != nil {
		return appErrors.NewValidationError("api_contracts.json", "failed to parse: "+err.Error())
	}

	header := "// Code generated by cmd/codegen. DO NOT EDIT.\n// Source: api_contracts.json\n// Generated at: 2026-01-01T00:00:00Z\n\n"

	// 1. Generate Go DTOs
	if err := generateGoDTOs(rootPath, header, contract); err != nil {
		return err
	}

	// 2. Generate TS Interfaces
	if err := generateTSInterfaces(rootPath, header, contract); err != nil {
		return err
	}

	return nil
}

func generateGoDTOs(rootPath, header string, contract APIContract) error {
	var sb strings.Builder
	sb.WriteString(header)
	sb.WriteString("package api\n\n")

	for _, def := range contract.Definitions {
		sb.WriteString(fmt.Sprintf("// %s\n", def.Name))
		if def.Description != "" {
			sb.WriteString(fmt.Sprintf("// %s\n", def.Description))
		}
		sb.WriteString(fmt.Sprintf("type %s struct {\n", def.Name))

		for _, field := range def.Fields {
			goType := mapGoType(field.Type)
			jsonTag := field.Name
			if !field.Required {
				jsonTag += ",omitempty"
				// Optional logic: Use pointers for optional fields?
				// For now, keep it simple, rely on zero values / omitempty logic or manual pointers if specified in type (e.g. *string)
				// But "generated-schema" often just keeps value types.
				// Let's stick to standard value types unless it's explicitly nullable.
			}

			fieldName := snakeToPascal(field.Name)
			sb.WriteString(fmt.Sprintf("\t%s %s `json:\"%s\"`\n", fieldName, goType, jsonTag))
		}
		sb.WriteString("}\n\n")
	}

	destPath := filepath.Join(rootPath, "shared/pkg/api/dtos.go")
	return os.WriteFile(destPath, []byte(sb.String()), 0644)
}

func generateTSInterfaces(rootPath, header string, contract APIContract) error {
	var sb strings.Builder
	sb.WriteString("/* eslint-disable @typescript-eslint/no-explicit-any */\n")
	sb.WriteString(strings.Replace(header, "//", "//", -1)) // Just keep comment style

	for _, def := range contract.Definitions {
		sb.WriteString(fmt.Sprintf("/**\n * %s\n */\n", def.Description))
		sb.WriteString(fmt.Sprintf("export interface %s {\n", def.Name))

		for _, field := range def.Fields {
			tsType := mapTSType(field.Type)
			optional := ""
			if !field.Required {
				optional = "?"
			}

			// JSON name is usually snake_case or whatever is in json.
			// In TS we might want camelCase? The definition has "object_api_name".
			// Go Struct uses "ObjectAPIName" -> json:"object_api_name".
			// TS Interface should match the JSON wire format: "object_api_name".
			// But traditionally TS uses camelCase.
			// However, if the backend sends snake_case, TS interface must define snake_case properties
			// unless we have a mapper. The `APIClient` does NOT map keys.
			// So strict aliasing means: TS keys == JSON keys == Go json tags.
			// Current definition uses snake_case keys.

			// For robustness, let's keep the defined name.
			sb.WriteString(fmt.Sprintf("  %s%s: %s;\n", field.Name, optional, tsType))
		}
		sb.WriteString("}\n\n")
	}

	destPath := filepath.Join(rootPath, "frontend/src/generated-api.ts")
	return os.WriteFile(destPath, []byte(sb.String()), 0644)
}

func mapGoType(t string) string {
	if strings.HasPrefix(t, "array<") {
		inner := strings.TrimSuffix(strings.TrimPrefix(t, "array<"), ">")
		return "[]" + mapGoType(inner)
	}
	if strings.HasPrefix(t, "map<") {
		// e.g. map<string,any> -> map[string]interface{}
		return "map[string]interface{}"
	}

	switch t {
	case "string":
		return "string"
	case "integer":
		return "int"
	case "boolean":
		return "bool"
	case "any":
		return "interface{}"
	default:
		return t // Assumes named struct
	}
}

func mapTSType(t string) string {
	if strings.HasPrefix(t, "array<") {
		inner := strings.TrimSuffix(strings.TrimPrefix(t, "array<"), ">")
		return mapTSType(inner) + "[]"
	}
	if strings.HasPrefix(t, "map<") {
		return "Record<string, any>"
	}

	switch t {
	case "string":
		return "string"
	case "integer":
		return "number"
	case "boolean":
		return "boolean"
	case "any":
		return "unknown"
	default:
		return t
	}
}
