package compiler

import (
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"sync/atomic"
	"time"
)

// SynorCompiler is the main compiler client.
type SynorCompiler struct {
	config     CompilerConfig
	httpClient *http.Client
	closed     atomic.Bool

	// Sub-clients
	Contracts  *ContractsClient
	Abi        *AbiClient
	Analysis   *AnalysisClient
	Validation *ValidationClient
}

// NewSynorCompiler creates a new compiler client.
func NewSynorCompiler(config CompilerConfig) *SynorCompiler {
	if config.Endpoint == "" {
		config.Endpoint = "https://compiler.synor.io/v1"
	}
	if config.Timeout == 0 {
		config.Timeout = 60000
	}
	if config.OptimizationLevel == "" {
		config.OptimizationLevel = OptimizationSize
	}

	c := &SynorCompiler{
		config: config,
		httpClient: &http.Client{
			Timeout: time.Duration(config.Timeout) * time.Millisecond,
		},
	}

	c.Contracts = &ContractsClient{compiler: c}
	c.Abi = &AbiClient{compiler: c}
	c.Analysis = &AnalysisClient{compiler: c}
	c.Validation = &ValidationClient{compiler: c}

	return c
}

// DefaultOptimizationLevel returns the default optimization level.
func (c *SynorCompiler) DefaultOptimizationLevel() OptimizationLevel {
	return c.config.OptimizationLevel
}

// HealthCheck checks service health.
func (c *SynorCompiler) HealthCheck(ctx context.Context) (bool, error) {
	var result map[string]interface{}
	if err := c.get(ctx, "/health", &result); err != nil {
		return false, nil
	}
	return result["status"] == "healthy", nil
}

// GetInfo returns service info.
func (c *SynorCompiler) GetInfo(ctx context.Context) (map[string]interface{}, error) {
	var result map[string]interface{}
	if err := c.get(ctx, "/info", &result); err != nil {
		return nil, err
	}
	return result, nil
}

// Close closes the client.
func (c *SynorCompiler) Close() {
	c.closed.Store(true)
}

// IsClosed returns whether the client is closed.
func (c *SynorCompiler) IsClosed() bool {
	return c.closed.Load()
}

// Compile compiles WASM bytecode.
func (c *SynorCompiler) Compile(ctx context.Context, wasm []byte, opts *CompilationRequest) (*CompilationResult, error) {
	wasmBase64 := base64.StdEncoding.EncodeToString(wasm)

	body := map[string]interface{}{
		"wasm": wasmBase64,
	}

	if opts != nil {
		if opts.OptimizationLevel != nil {
			body["optimization_level"] = *opts.OptimizationLevel
		} else {
			body["optimization_level"] = c.config.OptimizationLevel
		}
		if opts.StripOptions != nil {
			body["strip_options"] = opts.StripOptions
		}
		if opts.UseWasmOpt != nil {
			body["use_wasm_opt"] = *opts.UseWasmOpt
		} else {
			body["use_wasm_opt"] = c.config.UseWasmOpt
		}
		if opts.Validate != nil {
			body["validate"] = *opts.Validate
		} else {
			body["validate"] = c.config.Validate
		}
		if opts.ExtractMetadata != nil {
			body["extract_metadata"] = *opts.ExtractMetadata
		} else {
			body["extract_metadata"] = c.config.ExtractMetadata
		}
		if opts.GenerateAbi != nil {
			body["generate_abi"] = *opts.GenerateAbi
		} else {
			body["generate_abi"] = c.config.GenerateAbi
		}
	} else {
		body["optimization_level"] = c.config.OptimizationLevel
		body["use_wasm_opt"] = c.config.UseWasmOpt
		body["validate"] = c.config.Validate
		body["extract_metadata"] = c.config.ExtractMetadata
		body["generate_abi"] = c.config.GenerateAbi
	}

	var result CompilationResult
	if err := c.post(ctx, "/compile", body, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

func (c *SynorCompiler) get(ctx context.Context, path string, result interface{}) error {
	if c.closed.Load() {
		return &CompilerError{Message: "Client has been closed", Code: "CLIENT_CLOSED"}
	}

	req, err := http.NewRequestWithContext(ctx, "GET", c.config.Endpoint+path, nil)
	if err != nil {
		return err
	}

	c.setHeaders(req)
	return c.doRequest(req, result)
}

func (c *SynorCompiler) post(ctx context.Context, path string, body interface{}, result interface{}) error {
	if c.closed.Load() {
		return &CompilerError{Message: "Client has been closed", Code: "CLIENT_CLOSED"}
	}

	jsonBody, err := json.Marshal(body)
	if err != nil {
		return err
	}

	req, err := http.NewRequestWithContext(ctx, "POST", c.config.Endpoint+path, bytes.NewReader(jsonBody))
	if err != nil {
		return err
	}

	c.setHeaders(req)
	return c.doRequest(req, result)
}

func (c *SynorCompiler) setHeaders(req *http.Request) {
	req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-SDK-Version", "go/0.1.0")
}

func (c *SynorCompiler) doRequest(req *http.Request, result interface{}) error {
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	if resp.StatusCode >= 400 {
		var errResp map[string]interface{}
		if json.Unmarshal(respBody, &errResp) == nil {
			msg, _ := errResp["message"].(string)
			code, _ := errResp["code"].(string)
			if msg == "" {
				msg = fmt.Sprintf("HTTP %d", resp.StatusCode)
			}
			return &CompilerError{Message: msg, Code: code, HTTPStatus: resp.StatusCode}
		}
		return &CompilerError{
			Message:    fmt.Sprintf("HTTP %d: %s", resp.StatusCode, string(respBody)),
			HTTPStatus: resp.StatusCode,
		}
	}

	if result != nil && len(respBody) > 0 {
		return json.Unmarshal(respBody, result)
	}
	return nil
}

// ContractsClient is the contracts sub-client.
type ContractsClient struct {
	compiler *SynorCompiler
}

// Compile compiles WASM bytecode with default settings.
func (c *ContractsClient) Compile(ctx context.Context, wasm []byte, opts *CompilationRequest) (*CompilationResult, error) {
	return c.compiler.Compile(ctx, wasm, opts)
}

// CompileDev compiles with development settings.
func (c *ContractsClient) CompileDev(ctx context.Context, wasm []byte) (*CompilationResult, error) {
	level := OptimizationNone
	useWasmOpt := false
	validate := true
	return c.compiler.Compile(ctx, wasm, &CompilationRequest{
		OptimizationLevel: &level,
		UseWasmOpt:        &useWasmOpt,
		Validate:          &validate,
	})
}

// CompileProduction compiles with production settings.
func (c *ContractsClient) CompileProduction(ctx context.Context, wasm []byte) (*CompilationResult, error) {
	level := OptimizationAggressive
	useWasmOpt := true
	validate := true
	extractMetadata := true
	generateAbi := true
	return c.compiler.Compile(ctx, wasm, &CompilationRequest{
		OptimizationLevel: &level,
		UseWasmOpt:        &useWasmOpt,
		Validate:          &validate,
		ExtractMetadata:   &extractMetadata,
		GenerateAbi:       &generateAbi,
	})
}

// Get gets a compiled contract by ID.
func (c *ContractsClient) Get(ctx context.Context, contractID string) (*CompilationResult, error) {
	var result CompilationResult
	if err := c.compiler.get(ctx, "/contracts/"+contractID, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// List lists compiled contracts.
func (c *ContractsClient) List(ctx context.Context, limit, offset *int) ([]CompilationResult, error) {
	path := "/contracts"
	if limit != nil || offset != nil {
		path += "?"
		if limit != nil {
			path += fmt.Sprintf("limit=%d", *limit)
			if offset != nil {
				path += "&"
			}
		}
		if offset != nil {
			path += fmt.Sprintf("offset=%d", *offset)
		}
	}

	var result struct {
		Contracts []CompilationResult `json:"contracts"`
	}
	if err := c.compiler.get(ctx, path, &result); err != nil {
		return nil, err
	}
	return result.Contracts, nil
}

// GetCode gets optimized bytecode for a contract.
func (c *ContractsClient) GetCode(ctx context.Context, contractID string) ([]byte, error) {
	var result struct {
		Code string `json:"code"`
	}
	if err := c.compiler.get(ctx, "/contracts/"+contractID+"/code", &result); err != nil {
		return nil, err
	}
	return base64.StdEncoding.DecodeString(result.Code)
}

// AbiClient is the ABI sub-client.
type AbiClient struct {
	compiler *SynorCompiler
}

// Extract extracts ABI from WASM bytecode.
func (c *AbiClient) Extract(ctx context.Context, wasm []byte) (*ContractAbi, error) {
	wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
	var result ContractAbi
	if err := c.compiler.post(ctx, "/abi/extract", map[string]string{"wasm": wasmBase64}, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// Get gets ABI for a compiled contract.
func (c *AbiClient) Get(ctx context.Context, contractID string) (*ContractAbi, error) {
	var result ContractAbi
	if err := c.compiler.get(ctx, "/contracts/"+contractID+"/abi", &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// EncodeCall encodes a function call.
func (c *AbiClient) EncodeCall(ctx context.Context, functionAbi *FunctionAbi, args []interface{}) (string, error) {
	var result struct {
		Data string `json:"data"`
	}
	if err := c.compiler.post(ctx, "/abi/encode", map[string]interface{}{
		"function": functionAbi,
		"args":     args,
	}, &result); err != nil {
		return "", err
	}
	return result.Data, nil
}

// DecodeResult decodes a function result.
func (c *AbiClient) DecodeResult(ctx context.Context, functionAbi *FunctionAbi, data string) ([]interface{}, error) {
	var result struct {
		Values []interface{} `json:"values"`
	}
	if err := c.compiler.post(ctx, "/abi/decode", map[string]interface{}{
		"function": functionAbi,
		"data":     data,
	}, &result); err != nil {
		return nil, err
	}
	return result.Values, nil
}

// AnalysisClient is the analysis sub-client.
type AnalysisClient struct {
	compiler *SynorCompiler
}

// Analyze analyzes WASM bytecode.
func (c *AnalysisClient) Analyze(ctx context.Context, wasm []byte) (*ContractAnalysis, error) {
	wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
	var result ContractAnalysis
	if err := c.compiler.post(ctx, "/analysis", map[string]string{"wasm": wasmBase64}, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// Get gets analysis for a compiled contract.
func (c *AnalysisClient) Get(ctx context.Context, contractID string) (*ContractAnalysis, error) {
	var result ContractAnalysis
	if err := c.compiler.get(ctx, "/contracts/"+contractID+"/analysis", &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// ExtractMetadata extracts metadata from WASM bytecode.
func (c *AnalysisClient) ExtractMetadata(ctx context.Context, wasm []byte) (*ContractMetadata, error) {
	wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
	var result ContractMetadata
	if err := c.compiler.post(ctx, "/analysis/metadata", map[string]string{"wasm": wasmBase64}, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// ExtractSourceMap extracts source map from WASM bytecode.
func (c *AnalysisClient) ExtractSourceMap(ctx context.Context, wasm []byte) (*SourceMap, error) {
	wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
	var result struct {
		SourceMap *SourceMap `json:"source_map"`
	}
	if err := c.compiler.post(ctx, "/analysis/source-map", map[string]string{"wasm": wasmBase64}, &result); err != nil {
		return nil, err
	}
	return result.SourceMap, nil
}

// EstimateDeployGas estimates gas for contract deployment.
func (c *AnalysisClient) EstimateDeployGas(ctx context.Context, wasm []byte) (int64, error) {
	wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
	var result struct {
		Gas int64 `json:"gas"`
	}
	if err := c.compiler.post(ctx, "/analysis/estimate-gas", map[string]string{"wasm": wasmBase64}, &result); err != nil {
		return 0, err
	}
	return result.Gas, nil
}

// SecurityScan performs security analysis.
func (c *AnalysisClient) SecurityScan(ctx context.Context, wasm []byte) (*SecurityAnalysis, error) {
	wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
	var result struct {
		Security SecurityAnalysis `json:"security"`
	}
	if err := c.compiler.post(ctx, "/analysis/security", map[string]string{"wasm": wasmBase64}, &result); err != nil {
		return nil, err
	}
	return &result.Security, nil
}

// ValidationClient is the validation sub-client.
type ValidationClient struct {
	compiler *SynorCompiler
}

// Validate validates WASM bytecode against VM requirements.
func (c *ValidationClient) Validate(ctx context.Context, wasm []byte) (*ValidationResult, error) {
	wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
	var result ValidationResult
	if err := c.compiler.post(ctx, "/validate", map[string]string{"wasm": wasmBase64}, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// IsValid checks if WASM is valid.
func (c *ValidationClient) IsValid(ctx context.Context, wasm []byte) (bool, error) {
	result, err := c.Validate(ctx, wasm)
	if err != nil {
		return false, err
	}
	return result.Valid, nil
}

// GetErrors gets validation errors.
func (c *ValidationClient) GetErrors(ctx context.Context, wasm []byte) ([]string, error) {
	result, err := c.Validate(ctx, wasm)
	if err != nil {
		return nil, err
	}
	errors := make([]string, len(result.Errors))
	for i, e := range result.Errors {
		errors[i] = e.Message
	}
	return errors, nil
}

// ValidateExports validates contract exports.
func (c *ValidationClient) ValidateExports(ctx context.Context, wasm []byte, requiredExports []string) (bool, error) {
	wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
	var result struct {
		Valid bool `json:"valid"`
	}
	if err := c.compiler.post(ctx, "/validate/exports", map[string]interface{}{
		"wasm":             wasmBase64,
		"required_exports": requiredExports,
	}, &result); err != nil {
		return false, err
	}
	return result.Valid, nil
}

// ValidateMemory validates memory limits.
func (c *ValidationClient) ValidateMemory(ctx context.Context, wasm []byte, maxPages int) (bool, error) {
	wasmBase64 := base64.StdEncoding.EncodeToString(wasm)
	var result struct {
		Valid bool `json:"valid"`
	}
	if err := c.compiler.post(ctx, "/validate/memory", map[string]interface{}{
		"wasm":      wasmBase64,
		"max_pages": maxPages,
	}, &result); err != nil {
		return false, err
	}
	return result.Valid, nil
}
