package declarative

import (
	"context"
	"encoding/json"
	"testing"

	. "github.com/smartystreets/goconvey/convey"

	"github.com/authgear/authgear-server/pkg/api/model"
	"github.com/authgear/authgear-server/pkg/lib/config"
	"github.com/authgear/authgear-server/pkg/util/validation"
)

func TestInputSchemaLoginFlowStepAuthenticate(t *testing.T) {
	Convey("InputSchemaLoginFlowStepAuthenticate", t, func() {
		test := func(b validation.SchemaBuilder, expected string) {
			bytes, err := json.Marshal(b)
			So(err, ShouldBeNil)
			So(string(bytes), ShouldEqualJSON, expected)
		}

		Convey("device token is false when it is not enabled", func() {
			test((&InputSchemaLoginFlowStepAuthenticate{}).SchemaBuilder(), `
{
    "type": "object",
    "properties": {
        "authentication": {
            "enum": [
                []
            ]
        },
        "request_device_token": {
            "const": false,
            "type": "boolean"
        }
    },
    "required": [
        "authentication"
    ]
}
`)
		})

		Convey("device token is type boolean when it is enabled", func() {
			test((&InputSchemaLoginFlowStepAuthenticate{
				DeviceTokenEnabled: true,
			}).SchemaBuilder(), `
{
    "type": "object",
    "properties": {
        "authentication": {
            "enum": [
                []
            ]
        },
        "request_device_token": {
            "type": "boolean"
        }
    },
    "required": [
        "authentication"
    ]
}
`)
		})

		Convey("options", func() {
			test((&InputSchemaLoginFlowStepAuthenticate{
				Options: []AuthenticateOption{
					{
						Authentication: model.AuthenticationFlowAuthenticationPrimaryPassword,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationPrimaryPasskey,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationSecondaryPassword,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationSecondaryTOTP,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationPrimaryOOBOTPEmail,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationPrimaryOOBOTPSMS,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationSecondaryOOBOTPEmail,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationSecondaryOOBOTPSMS,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationRecoveryCode,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationDeviceToken,
					},
				},
			}).SchemaBuilder(), `
{
    "type": "object",
    "properties": {
        "request_device_token": {
            "const": false,
            "type": "boolean"
        }
    },
    "oneOf": [
        {
            "properties": {
                "authentication": {
                    "const": "primary_password"
                },
                "password": {
                    "type": "string"
                }
            },
            "required": [
                "authentication",
                "password"
            ]
        },
        {
            "properties": {
                "assertion_response": {
                    "properties": {
                        "clientExtensionResults": {
                            "type": "object"
                        },
                        "id": {
                            "type": "string"
                        },
                        "rawId": {
                            "format": "x_base64_url",
                            "type": "string"
                        },
                        "response": {
                            "properties": {
                                "authenticatorData": {
                                    "format": "x_base64_url",
                                    "type": "string"
                                },
                                "clientDataJSON": {
                                    "format": "x_base64_url",
                                    "type": "string"
                                },
                                "signature": {
                                    "format": "x_base64_url",
                                    "type": "string"
                                },
                                "userHandle": {
                                    "format": "x_base64_url",
                                    "type": "string"
                                }
                            },
                            "required": [
                                "clientDataJSON",
                                "authenticatorData",
                                "signature"
                            ],
                            "type": "object"
                        },
                        "type": {
                            "type": "string"
                        }
                    },
                    "required": [
                        "id",
                        "type",
                        "rawId",
                        "response"
                    ],
                    "type": "object"
                },
                "authentication": {
                    "const": "primary_passkey"
                }
            },
            "required": [
                "authentication",
                "assertion_response"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "secondary_password"
                },
                "password": {
                    "type": "string"
                }
            },
            "required": [
                "authentication",
                "password"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "secondary_totp"
                },
                "code": {
                    "type": "string"
                }
            },
            "required": [
                "authentication",
                "code"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "primary_oob_otp_email"
                },
                "index": {
                    "const": 4,
                    "type": "integer"
                }
            },
            "required": [
                "authentication",
                "index"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "primary_oob_otp_sms"
                },
                "index": {
                    "const": 5,
                    "type": "integer"
                }
            },
            "required": [
                "authentication",
                "index"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "secondary_oob_otp_email"
                },
                "index": {
                    "const": 6,
                    "type": "integer"
                }
            },
            "required": [
                "authentication",
                "index"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "secondary_oob_otp_sms"
                },
                "index": {
                    "const": 7,
                    "type": "integer"
                }
            },
            "required": [
                "authentication",
                "index"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "recovery_code"
                },
                "recovery_code": {
                    "type": "string"
                }
            },
            "required": [
                "authentication",
                "recovery_code"
            ]
        }
    ]
}
`)
		})
		Convey("options with bot protections should require bot protection input", func() {
			var varTrue = true
			var dummyBotProtectionData = &BotProtectionData{
				Enabled: &varTrue,
				Provider: &BotProtectionDataProvider{
					Type: config.BotProtectionProviderTypeCloudflare,
				},
			}
			var dummyBotProtectionCfg = &config.BotProtectionConfig{
				Enabled: true,
				Provider: &config.BotProtectionProvider{
					Type: config.BotProtectionProviderTypeCloudflare,
				},
			}
			test((&InputSchemaLoginFlowStepAuthenticate{
				BotProtectionCfg: dummyBotProtectionCfg,
				Options: []AuthenticateOption{
					{
						Authentication: model.AuthenticationFlowAuthenticationPrimaryPassword,
						BotProtection:  dummyBotProtectionData,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationPrimaryPasskey,
						BotProtection:  dummyBotProtectionData,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationSecondaryPassword,
						BotProtection:  dummyBotProtectionData,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationSecondaryTOTP,
						BotProtection:  dummyBotProtectionData,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationPrimaryOOBOTPEmail,
						BotProtection:  dummyBotProtectionData,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationPrimaryOOBOTPSMS,
						BotProtection:  dummyBotProtectionData,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationSecondaryOOBOTPEmail,
						BotProtection:  dummyBotProtectionData,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationSecondaryOOBOTPSMS,
						BotProtection:  dummyBotProtectionData,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationRecoveryCode,
						BotProtection:  dummyBotProtectionData,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationDeviceToken,
						BotProtection:  dummyBotProtectionData,
					},
				},
			}).SchemaBuilder(), `
{
    "type": "object",
    "properties": {
        "request_device_token": {
            "const": false,
            "type": "boolean"
        }
    },
    "oneOf": [
        {
            "properties": {
                "authentication": {
                    "const": "primary_password"
                },
                "bot_protection": {
                    "properties": {
                        "response": {
                            "type": "string"
                        },
                        "type": {
                            "const": "cloudflare"
                        }
                    },
                    "required": [
                        "type",
                        "response"
                    ],
                    "type": "object"
                },
                "password": {
                    "type": "string"
                }
            },
            "required": [
                "authentication",
                "bot_protection",
                "password"
            ]
        },
        {
            "properties": {
                "assertion_response": {
                    "properties": {
                        "clientExtensionResults": {
                            "type": "object"
                        },
                        "id": {
                            "type": "string"
                        },
                        "rawId": {
                            "format": "x_base64_url",
                            "type": "string"
                        },
                        "response": {
                            "properties": {
                                "authenticatorData": {
                                    "format": "x_base64_url",
                                    "type": "string"
                                },
                                "clientDataJSON": {
                                    "format": "x_base64_url",
                                    "type": "string"
                                },
                                "signature": {
                                    "format": "x_base64_url",
                                    "type": "string"
                                },
                                "userHandle": {
                                    "format": "x_base64_url",
                                    "type": "string"
                                }
                            },
                            "required": [
                                "clientDataJSON",
                                "authenticatorData",
                                "signature"
                            ],
                            "type": "object"
                        },
                        "type": {
                            "type": "string"
                        }
                    },
                    "required": [
                        "id",
                        "type",
                        "rawId",
                        "response"
                    ],
                    "type": "object"
                },
                "authentication": {
                    "const": "primary_passkey"
                },
                "bot_protection": {
                    "properties": {
                        "response": {
                            "type": "string"
                        },
                        "type": {
                            "const": "cloudflare"
                        }
                    },
                    "required": [
                        "type",
                        "response"
                    ],
                    "type": "object"
                }
            },
            "required": [
                "authentication",
                "bot_protection",
                "assertion_response"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "secondary_password"
                },
                "bot_protection": {
                    "properties": {
                        "response": {
                            "type": "string"
                        },
                        "type": {
                            "const": "cloudflare"
                        }
                    },
                    "required": [
                        "type",
                        "response"
                    ],
                    "type": "object"
                },
                "password": {
                    "type": "string"
                }
            },
            "required": [
                "authentication",
                "bot_protection",
                "password"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "secondary_totp"
                },
                "bot_protection": {
                    "properties": {
                        "response": {
                            "type": "string"
                        },
                        "type": {
                            "const": "cloudflare"
                        }
                    },
                    "required": [
                        "type",
                        "response"
                    ],
                    "type": "object"
                },
                "code": {
                    "type": "string"
                }
            },
            "required": [
                "authentication",
                "bot_protection",
                "code"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "primary_oob_otp_email"
                },
                "bot_protection": {
                    "properties": {
                        "response": {
                            "type": "string"
                        },
                        "type": {
                            "const": "cloudflare"
                        }
                    },
                    "required": [
                        "type",
                        "response"
                    ],
                    "type": "object"
                },
                "index": {
                    "const": 4,
                    "type": "integer"
                }
            },
            "required": [
                "authentication",
                "bot_protection",
                "index"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "primary_oob_otp_sms"
                },
                "bot_protection": {
                    "properties": {
                        "response": {
                            "type": "string"
                        },
                        "type": {
                            "const": "cloudflare"
                        }
                    },
                    "required": [
                        "type",
                        "response"
                    ],
                    "type": "object"
                },
                "index": {
                    "const": 5,
                    "type": "integer"
                }
            },
            "required": [
                "authentication",
                "bot_protection",
                "index"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "secondary_oob_otp_email"
                },
                "bot_protection": {
                    "properties": {
                        "response": {
                            "type": "string"
                        },
                        "type": {
                            "const": "cloudflare"
                        }
                    },
                    "required": [
                        "type",
                        "response"
                    ],
                    "type": "object"
                },
                "index": {
                    "const": 6,
                    "type": "integer"
                }
            },
            "required": [
                "authentication",
                "bot_protection",
                "index"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "secondary_oob_otp_sms"
                },
                "bot_protection": {
                    "properties": {
                        "response": {
                            "type": "string"
                        },
                        "type": {
                            "const": "cloudflare"
                        }
                    },
                    "required": [
                        "type",
                        "response"
                    ],
                    "type": "object"
                },
                "index": {
                    "const": 7,
                    "type": "integer"
                }
            },
            "required": [
                "authentication",
                "bot_protection",
                "index"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "recovery_code"
                },
                "bot_protection": {
                    "properties": {
                        "response": {
                            "type": "string"
                        },
                        "type": {
                            "const": "cloudflare"
                        }
                    },
                    "required": [
                        "type",
                        "response"
                    ],
                    "type": "object"
                },
                "recovery_code": {
                    "type": "string"
                }
            },
            "required": [
                "authentication",
                "bot_protection",
                "recovery_code"
            ]
        }
    ]
}
`)
		})
		Convey("only options w/ bot protections should require bot protection input, options w/o bot protections should not require bot protection input", func() {
			var varTrue = true
			var dummyBotProtectionData = &BotProtectionData{
				Enabled: &varTrue,
				Provider: &BotProtectionDataProvider{
					Type: config.BotProtectionProviderTypeCloudflare,
				},
			}
			var dummyBotProtectionCfg = &config.BotProtectionConfig{
				Enabled: true,
				Provider: &config.BotProtectionProvider{
					Type: config.BotProtectionProviderTypeCloudflare,
				},
			}
			test((&InputSchemaLoginFlowStepAuthenticate{
				BotProtectionCfg: dummyBotProtectionCfg,
				Options: []AuthenticateOption{
					{
						Authentication: model.AuthenticationFlowAuthenticationPrimaryPassword,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationPrimaryPasskey,
						BotProtection:  dummyBotProtectionData,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationSecondaryPassword,
						BotProtection:  dummyBotProtectionData,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationSecondaryTOTP,
						BotProtection:  dummyBotProtectionData,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationPrimaryOOBOTPEmail,
						BotProtection:  dummyBotProtectionData,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationPrimaryOOBOTPSMS,
						BotProtection:  dummyBotProtectionData,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationSecondaryOOBOTPEmail,
						BotProtection:  dummyBotProtectionData,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationSecondaryOOBOTPSMS,
						BotProtection:  dummyBotProtectionData,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationRecoveryCode,
					},
					{
						Authentication: model.AuthenticationFlowAuthenticationDeviceToken,
						BotProtection:  dummyBotProtectionData,
					},
				},
			}).SchemaBuilder(), `
{
    "type": "object",
    "properties": {
        "request_device_token": {
            "const": false,
            "type": "boolean"
        }
    },
    "oneOf": [
        {
            "properties": {
                "authentication": {
                    "const": "primary_password"
                },
                "password": {
                    "type": "string"
                }
            },
            "required": [
                "authentication",
                "password"
            ]
        },
        {
            "properties": {
                "assertion_response": {
                    "properties": {
                        "clientExtensionResults": {
                            "type": "object"
                        },
                        "id": {
                            "type": "string"
                        },
                        "rawId": {
                            "format": "x_base64_url",
                            "type": "string"
                        },
                        "response": {
                            "properties": {
                                "authenticatorData": {
                                    "format": "x_base64_url",
                                    "type": "string"
                                },
                                "clientDataJSON": {
                                    "format": "x_base64_url",
                                    "type": "string"
                                },
                                "signature": {
                                    "format": "x_base64_url",
                                    "type": "string"
                                },
                                "userHandle": {
                                    "format": "x_base64_url",
                                    "type": "string"
                                }
                            },
                            "required": [
                                "clientDataJSON",
                                "authenticatorData",
                                "signature"
                            ],
                            "type": "object"
                        },
                        "type": {
                            "type": "string"
                        }
                    },
                    "required": [
                        "id",
                        "type",
                        "rawId",
                        "response"
                    ],
                    "type": "object"
                },
                "authentication": {
                    "const": "primary_passkey"
                },
                "bot_protection": {
                    "properties": {
                        "response": {
                            "type": "string"
                        },
                        "type": {
                            "const": "cloudflare"
                        }
                    },
                    "required": [
                        "type",
                        "response"
                    ],
                    "type": "object"
                }
            },
            "required": [
                "authentication",
                "bot_protection",
                "assertion_response"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "secondary_password"
                },
                "bot_protection": {
                    "properties": {
                        "response": {
                            "type": "string"
                        },
                        "type": {
                            "const": "cloudflare"
                        }
                    },
                    "required": [
                        "type",
                        "response"
                    ],
                    "type": "object"
                },
                "password": {
                    "type": "string"
                }
            },
            "required": [
                "authentication",
                "bot_protection",
                "password"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "secondary_totp"
                },
                "bot_protection": {
                    "properties": {
                        "response": {
                            "type": "string"
                        },
                        "type": {
                            "const": "cloudflare"
                        }
                    },
                    "required": [
                        "type",
                        "response"
                    ],
                    "type": "object"
                },
                "code": {
                    "type": "string"
                }
            },
            "required": [
                "authentication",
                "bot_protection",
                "code"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "primary_oob_otp_email"
                },
                "bot_protection": {
                    "properties": {
                        "response": {
                            "type": "string"
                        },
                        "type": {
                            "const": "cloudflare"
                        }
                    },
                    "required": [
                        "type",
                        "response"
                    ],
                    "type": "object"
                },
                "index": {
                    "const": 4,
                    "type": "integer"
                }
            },
            "required": [
                "authentication",
                "bot_protection",
                "index"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "primary_oob_otp_sms"
                },
                "bot_protection": {
                    "properties": {
                        "response": {
                            "type": "string"
                        },
                        "type": {
                            "const": "cloudflare"
                        }
                    },
                    "required": [
                        "type",
                        "response"
                    ],
                    "type": "object"
                },
                "index": {
                    "const": 5,
                    "type": "integer"
                }
            },
            "required": [
                "authentication",
                "bot_protection",
                "index"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "secondary_oob_otp_email"
                },
                "bot_protection": {
                    "properties": {
                        "response": {
                            "type": "string"
                        },
                        "type": {
                            "const": "cloudflare"
                        }
                    },
                    "required": [
                        "type",
                        "response"
                    ],
                    "type": "object"
                },
                "index": {
                    "const": 6,
                    "type": "integer"
                }
            },
            "required": [
                "authentication",
                "bot_protection",
                "index"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "secondary_oob_otp_sms"
                },
                "bot_protection": {
                    "properties": {
                        "response": {
                            "type": "string"
                        },
                        "type": {
                            "const": "cloudflare"
                        }
                    },
                    "required": [
                        "type",
                        "response"
                    ],
                    "type": "object"
                },
                "index": {
                    "const": 7,
                    "type": "integer"
                }
            },
            "required": [
                "authentication",
                "bot_protection",
                "index"
            ]
        },
        {
            "properties": {
                "authentication": {
                    "const": "recovery_code"
                },
                "recovery_code": {
                    "type": "string"
                }
            },
            "required": [
                "authentication",
                "recovery_code"
            ]
        }
    ]
}
`)
		})

		Convey("should reject any input when no options", func() {
			var dummyBotProtectionCfg = &config.BotProtectionConfig{
				Enabled: true,
				Provider: &config.BotProtectionProvider{
					Type: config.BotProtectionProviderTypeCloudflare,
				},
			}
			var input = &InputSchemaLoginFlowStepAuthenticate{
				BotProtectionCfg: dummyBotProtectionCfg,
				Options:          []AuthenticateOption{},
			}
			inputJson := `
                {
                    "authentication": "primary_password"
                }
            `
			_, err := input.MakeInput(context.Background(), json.RawMessage(inputJson))
			So(err, ShouldBeError, "invalid value:\n/authentication: enum\n  map[actual:primary_password expected:[[]]]")
		})
	})
}
