package javagen

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"strings"
	"text/template"

	"github.com/tal-tech/go-zero/core/stringx"
	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
	apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
	"github.com/tal-tech/go-zero/tools/goctl/util"
)

const packetTemplate = `package com.xhb.logic.http.packet.{{.packet}};

import com.google.gson.Gson;
import com.xhb.commons.JSON;
import com.xhb.commons.JsonParser;
import com.xhb.core.network.HttpRequestClient;
import com.xhb.core.packet.HttpRequestPacket;
import com.xhb.core.response.HttpResponseData;
import com.xhb.logic.http.DeProguardable;
{{.import}}

import org.jetbrains.annotations.NotNull;
import org.json.JSONObject;

public class {{.packetName}} extends HttpRequestPacket<{{.packetName}}.{{.packetName}}Response> {

	{{.paramsDeclaration}}

	public {{.packetName}}({{.params}}{{.requestType}} request) {
        super(request);
		this.request = request;{{.paramsSet}}
    }

	@Override
    public HttpRequestClient.Method requestMethod() {
        return HttpRequestClient.Method.{{.method}};
    }

	@Override
    public String requestUri() {
        return {{.uri}};
    }

	@Override
    public {{.packetName}}Response newInstanceFrom(JSON json) {
        return new {{.packetName}}Response(json);
    }

	public static class {{.packetName}}Response extends HttpResponseData {

		private {{.responseType}} responseData;

        {{.packetName}}Response(@NotNull JSON json) {
            super(json);
            JSONObject jsonObject = json.asObject();
			if (JsonParser.hasKey(jsonObject, "data")) {
				Gson gson = new Gson();
				JSONObject dataJson = JsonParser.getJSONObject(jsonObject, "data");
				responseData = gson.fromJson(dataJson.toString(), {{.responseType}}.class);
			}
        }

		public {{.responseType}} get{{.responseType}} () {
            return responseData;
        }
    }

	{{.types}}
}
`

func genPacket(dir, packetName string, api *spec.ApiSpec) error {
	for _, route := range api.Service.Routes() {
		if err := createWith(dir, api, route, packetName); err != nil {
			return err
		}
	}

	return nil
}

func createWith(dir string, api *spec.ApiSpec, route spec.Route, packetName string) error {
	packet, ok := apiutil.GetAnnotationValue(route.Annotations, "server", "handler")
	packet = strings.Replace(packet, "Handler", "Packet", 1)
	if !ok {
		return fmt.Errorf("missing packet annotation for %q", route.Path)
	}

	javaFile := packet + ".java"
	fp, created, err := apiutil.MaybeCreateFile(dir, "", javaFile)
	if err != nil {
		return err
	}
	if !created {
		return nil
	}
	defer fp.Close()

	var builder strings.Builder
	var first bool
	tps := apiutil.GetLocalTypes(api, route)

	for _, tp := range tps {
		if first {
			first = false
		} else {
			fmt.Fprintln(&builder)
		}
		if err := genType(&builder, tp); err != nil {
			return err
		}
	}
	types := builder.String()
	writeIndent(&builder, 1)

	params := paramsForRoute(route)
	paramsDeclaration := declarationForRoute(route)
	paramsSet := paramsSet(route)

	t := template.Must(template.New("packetTemplate").Parse(packetTemplate))
	var tmplBytes bytes.Buffer
	err = t.Execute(&tmplBytes, map[string]string{
		"packetName":        packet,
		"method":            strings.ToUpper(route.Method),
		"uri":               processUri(route),
		"types":             strings.TrimSpace(types),
		"responseType":      stringx.TakeOne(util.Title(route.ResponseType.Name), "Object"),
		"params":            params,
		"paramsDeclaration": strings.TrimSpace(paramsDeclaration),
		"paramsSet":         paramsSet,
		"packet":            packetName,
		"requestType":       util.Title(route.RequestType.Name),
		"import":            getImports(api, route, packetName),
	})
	if err != nil {
		return err
	}
	formatFile(&tmplBytes, fp)
	return nil
}

func getImports(api *spec.ApiSpec, route spec.Route, packetName string) string {
	var builder strings.Builder
	allTypes := apiutil.GetAllTypes(api, route)
	sharedTypes := apiutil.GetSharedTypes(api)
	for _, at := range allTypes {
		for _, item := range sharedTypes {
			if item.Name == at.Name {
				fmt.Fprintf(&builder, "import com.xhb.logic.http.packet.%s.model.%s;\n", packetName, item.Name)
				break
			}
		}
	}
	return builder.String()
}

func formatFile(tmplBytes *bytes.Buffer, file *os.File) {
	scanner := bufio.NewScanner(tmplBytes)
	builder := bufio.NewWriter(file)
	defer builder.Flush()
	preIsBreakLine := false
	for scanner.Scan() {
		text := strings.TrimSpace(scanner.Text())
		if text == "" && preIsBreakLine {
			continue
		}
		preIsBreakLine = text == ""
		builder.WriteString(scanner.Text() + "\n")
	}
	if err := scanner.Err(); err != nil {
		fmt.Println(err)
	}
}

func paramsSet(route spec.Route) string {
	path := route.Path
	cops := strings.Split(path, "/")
	var builder strings.Builder
	for _, cop := range cops {
		if len(cop) == 0 {
			continue
		}
		if strings.HasPrefix(cop, ":") {
			param := cop[1:]
			builder.WriteString("\n")
			builder.WriteString(fmt.Sprintf("\t\tthis.%s = %s;", param, param))
		}
	}
	result := builder.String()
	return result
}

func paramsForRoute(route spec.Route) string {
	path := route.Path
	cops := strings.Split(path, "/")
	var builder strings.Builder
	for _, cop := range cops {
		if len(cop) == 0 {
			continue
		}
		if strings.HasPrefix(cop, ":") {
			builder.WriteString(fmt.Sprintf("String %s, ", cop[1:]))
		}
	}
	return builder.String()
}

func declarationForRoute(route spec.Route) string {
	path := route.Path
	cops := strings.Split(path, "/")
	var builder strings.Builder
	writeIndent(&builder, 1)
	for _, cop := range cops {
		if len(cop) == 0 {
			continue
		}
		if strings.HasPrefix(cop, ":") {
			writeIndent(&builder, 1)
			builder.WriteString(fmt.Sprintf("private String %s;\n", cop[1:]))
		}
	}
	result := strings.TrimSpace(builder.String())
	if len(result) > 0 {
		result = "\n" + result
	}
	return result
}

func processUri(route spec.Route) string {
	path := route.Path
	var builder strings.Builder
	cops := strings.Split(path, "/")
	for index, cop := range cops {
		if len(cop) == 0 {
			continue
		}
		if strings.HasPrefix(cop, ":") {
			builder.WriteString("/\" + " + cop[1:] + " + \"")
		} else {
			builder.WriteString("/" + cop)
			if index == len(cops)-1 {
				builder.WriteString("\"")
			}
		}
	}
	result := builder.String()
	if strings.HasSuffix(result, " + \"") {
		result = result[:len(result)-4]
	}
	if strings.HasPrefix(result, "/") {
		result = "\"" + result
	}
	return result
}

func genType(writer io.Writer, tp spec.Type) error {
	writeIndent(writer, 1)
	fmt.Fprintf(writer, "static class %s implements DeProguardable {\n", util.Title(tp.Name))
	for _, member := range tp.Members {
		if err := writeProperty(writer, member, 2); err != nil {
			return err
		}
	}

	writeBreakline(writer)
	writeIndent(writer, 1)
	genGetSet(writer, tp, 2)
	writeIndent(writer, 1)
	fmt.Fprintln(writer, "}")

	return nil
}
