package api

import (
	"context"
	"fmt"

	"github.com/juju/errors"
	"github.com/machinebox/graphql"
	"github.com/pingcap/log"
	"go.uber.org/zap"
)

type DatasetResult struct {
	Dataset struct {
		Urn      string `json:"urn"`
		Name     string `json:"name"`
		Type     string `json:"type"`
		Platform struct {
			Name string `json:"name"`
		} `json:"platform"`
		Properties struct {
			Name        string `json:"name"`
			Description string `json:"description"`
		} `json:"properties"`
		EditableProperties struct {
			Description string `json:"description"`
		} `json:"editableProperties"`
		EditableSchemaMetadata struct {
			EditableSchemaFieldInfo []struct {
				FieldPath   string `json:"fieldPath"`
				Description string `json:"description"`
				Tags        struct {
					Tags []struct {
						Tag struct {
							Urn        string `json:"urn"`
							Type       string `json:"type"`
							Properties struct {
								Name        string `json:"name"`
								Description string `json:"description"`
							} `json:"properties"`
						} `json:"tag"`
					} `json:"tags"`
				} `json:"tags"`
			} `json:"editableSchemaFieldInfo"`
		} `json:"editableSchemaMetadata"`
		SchemaMetadata struct {
			PrimaryKeys string `json:"primaryKeys"`
			ForeignKeys []struct {
				ForeignFields []struct {
					FieldPath string `json:"fieldPath"`
				} `json:"foreignFields"`
				ForeignDataset struct {
					Name string `json:"name"`
				} `json:"foreignDataset"`
				SourceFields []struct {
					FieldPath string `json:"fieldPath"`
				} `json:"sourceFields"`
				Name string `json:"name"`
			} `json:"foreignKeys"`
			Fields []struct {
				FieldPath   string `json:"fieldPath"`
				Type        string `json:"type"`
				Label       string `json:"label"`
				Description string `json:"description"`
			} `json:"fields"`
		} `json:"schemaMetadata"`
		Tags struct {
			Tags []struct {
				Tag struct {
					Urn        string `json:"urn"`
					Type       string `json:"type"`
					Properties struct {
						Name        string `json:"name"`
						Description string `json:"description"`
					} `json:"properties"`
				} `json:"tag"`
			} `json:"tags"`
		} `json:"tags"`
	}
}

func (cli *DatahubClient) FetchDataset(dataset string) (*DatasetResult, error) {
	// Set up the client
	graphqlCli := graphql.NewClient(cli.APIEndpoint)
	// Set up the query
	req := graphql.NewRequest(`
		query ($urn: String!)  {
			dataset(urn: $urn) {
				urn
				name
				type
				platform {
					name
				}
				properties {
					name
					description
				}
				editableProperties {
				  	description
				}
				editableSchemaMetadata {
				  editableSchemaFieldInfo {
					fieldPath
					description
					tags {
					  tags {
						tag {
						  urn
						  type
						  properties {
							name
							description
						  }
						}
					  }
					}
				  }
				}
				schemaMetadata {
				  primaryKeys
				  foreignKeys {
					foreignFields{
					  fieldPath
					}
					foreignDataset{
					  name
					}
					sourceFields{
					  fieldPath
					}
					name
					
				  }
				  fields {
					fieldPath
					type
					label
					description
				  }
				}
				tags {
				  tags {
					tag {
					  urn
					  type
					  properties {
						name
						description
					  }
					}
				  }
				}
			  }
		}
	`)
	req.Var("urn", dataset)
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", cli.token))

	// Send the query to the server
	ctx := context.Background()
	//var respData map[string]interface{}
	var respData DatasetResult
	if err := graphqlCli.Run(ctx, req, &respData); err != nil {
		return nil, errors.Annotatef(err, "fail to fetch dataset info %s", dataset)
	}

	return &respData, nil
}

type UpdateDescriptionResponse struct {
	UpdateDescription bool `json:"updateDescription"`
}

func (cli *DatahubClient) UpdateFieldDescription(fieldPath, description, urn string) error {
	// Set up the client
	graphqlCli := graphql.NewClient(cli.APIEndpoint)
	// Set up the query
	req := graphql.NewRequest(`
		mutation updateDescription($field: String, $desciption: String!, $resourceUrn: String!) {
			updateDescription(input: { 
		  		subResourceType: DATASET_FIELD,
		  		subResource: $field, 
		  		description: $desciption, 
		  		resourceUrn: $resourceUrn 
			})
		}
	`)
	req.Var("field", fieldPath)
	req.Var("desciption", description)
	req.Var("resourceUrn", urn)
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", cli.token))

	// Send the query to the server
	ctx := context.Background()
	//var respData map[string]interface{}
	var respData UpdateDescriptionResponse
	if err := graphqlCli.Run(ctx, req, &respData); err != nil {
		return errors.Annotatef(err, "fail to update dataset description %s", urn)
	}

	log.Debug("query result", zap.Any("result", respData))
	if !respData.UpdateDescription {
		return errors.New("update description is not successful")
	}

	return nil
}

type AddTagResponse struct {
	AddTag bool `json:"addTag"`
}

func (cli *DatahubClient) AddTag(fieldPath, tag, urn string) error {
	// Set up the client
	graphqlCli := graphql.NewClient(cli.APIEndpoint)
	// Set up the query
	var req *graphql.Request
	if len(fieldPath) > 0 {
		// add tag for filepath
		req = graphql.NewRequest(`
			mutation updateDescription($field: String, $tagUrn: String!, $resourceUrn: String!) {
				addTag(input: {
					subResourceType: DATASET_FIELD,
					subResource: $field,
					tagUrn: $tagUrn,
					resourceUrn: $resourceUrn
				})
			}
		`)
		req.Var("field", fieldPath)
	} else {
		// add tag for table
		req = graphql.NewRequest(`
			mutation updateDescription($tagUrn: String!, $resourceUrn: String!) {
				addTag(input: {
					tagUrn: $tagUrn,
					resourceUrn: $resourceUrn
				})
			}
		`)
	}

	req.Var("tagUrn", tag)
	req.Var("resourceUrn", urn)
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", cli.token))

	// Send the query to the server
	ctx := context.Background()
	//var respData map[string]interface{}
	var respData AddTagResponse
	if err := graphqlCli.Run(ctx, req, &respData); err != nil {
		return errors.Annotatef(err, "fail to add tag for %s", urn)
	}

	log.Debug("query result", zap.Any("result", respData))
	if !respData.AddTag {
		return errors.New("add tag is not successful")
	}

	return nil
}
