package page

import (
	"fmt"
	"image/color"
	"log"
	"net"
	"strings"

	"gioui.org/layout"
	"gioui.org/widget"
	"gioui.org/widget/material"
	"gioui.org/x/component"
	"github.com/go-gost/gost-plus/tunnel"
	"github.com/go-gost/gost-plus/ui/icons"
	"golang.org/x/exp/shiny/materialdesign/colornames"
)

type httpAddPage struct {
	router *Router

	list   layout.List
	wgDone widget.Clickable

	name component.TextField
	addr component.TextField

	bHost    widget.Bool
	hostname component.TextField

	bBasicAuth widget.Bool
	username   component.TextField
	password   component.TextField

	bTLS widget.Bool

	wgPassword      widget.Clickable
	passwordVisible bool
}

func NewHTTPAddPage(r *Router) Page {
	return &httpAddPage{
		router: r,
		list: layout.List{
			Axis:      layout.Vertical,
			Alignment: layout.Middle,
		},
		name: component.TextField{
			Editor: widget.Editor{
				SingleLine: true,
			},
		},
		addr: component.TextField{
			Editor: widget.Editor{
				SingleLine: true,
			},
		},
		hostname: component.TextField{
			Editor: widget.Editor{
				SingleLine: true,
			},
		},
		username: component.TextField{
			Editor: widget.Editor{
				SingleLine: true,
			},
		},
		password: component.TextField{
			Editor: widget.Editor{
				SingleLine: true,
			},
		},
	}
}

func (p *httpAddPage) Init(opts ...PageOption) {
	p.name.Clear()
	p.addr.Clear()
	p.hostname.Clear()
	p.bBasicAuth.Value = false
	p.username.Clear()
	p.password.Clear()
	p.bTLS.Value = false
	p.passwordVisible = false

	p.router.bar.SetActions(
		[]component.AppBarAction{
			{
				OverflowAction: component.OverflowAction{
					Name: "Create",
					Tag:  &p.wgDone,
				},
				Layout: func(gtx C, bg, fg color.NRGBA) D {
					if p.wgDone.Clicked(gtx) {
						defer p.router.SwitchTo(Route{Path: PageTunnel})
						p.createTunnel()
						tunnel.SaveConfig()
					}
					return component.SimpleIconButton(bg, fg, &p.wgDone, icons.IconDone).Layout(gtx)
				},
			},
		}, nil)

	p.router.bar.Title = "HTTP"
	p.router.bar.NavigationIcon = icons.IconClose
}

func (p *httpAddPage) Layout(gtx C, th *material.Theme) D {
	return p.list.Layout(gtx, 1, func(gtx C, _ int) D {
		return layout.Center.Layout(gtx, func(gtx C) D {
			return layout.UniformInset(10).Layout(gtx, func(gtx C) D {
				return component.Surface(th).Layout(gtx, func(gtx C) D {
					return layout.UniformInset(10).Layout(gtx, func(gtx C) D {
						return p.layout(gtx, th)
					})
				})
			})
		})
	})
}

func (p *httpAddPage) layout(gtx C, th *material.Theme) D {
	return layout.Flex{
		Axis: layout.Vertical,
	}.Layout(gtx,
		layout.Rigid(material.Body1(th, "Expose local http tunnel to public network.").Layout),
		layout.Rigid(layout.Spacer{Height: 10}.Layout),
		layout.Rigid(func(gtx C) D {
			return material.Body1(th, "Tunnel name").Layout(gtx)
		}),
		layout.Rigid(func(gtx C) D {
			return p.name.Layout(gtx, th, "Name")
		}),
		layout.Rigid(layout.Spacer{Height: 10}.Layout),
		layout.Rigid(func(gtx C) D {
			return material.Body1(th, "Endpoint address").Layout(gtx)
		}),
		layout.Rigid(func(gtx C) D {
			if err := func() error {
				addr := strings.TrimSpace(p.addr.Text())
				if addr == "" {
					return nil
				}
				if _, _, err := net.SplitHostPort(addr); err != nil {
					return fmt.Errorf("invalid address format, should be [IP]:PORT or [HOST]:PORT")
				}
				return nil
			}(); err != nil {
				p.addr.SetError(err.Error())
			} else {
				p.addr.ClearError()
			}

			return p.addr.Layout(gtx, th, "Address")
		}),
		layout.Rigid(layout.Spacer{Height: 10}.Layout),
		layout.Rigid(func(gtx C) D {
			return layout.Flex{
				Spacing: layout.SpaceBetween,
			}.Layout(gtx,
				layout.Flexed(1, material.Body1(th, "Use custom hostname (rewrite HTTP Host header)").Layout),
				layout.Rigid(material.Switch(th, &p.bHost, "custom hostname").Layout),
			)
		}),
		layout.Rigid(func(gtx C) D {
			if !p.bHost.Value {
				p.hostname.SetText("")
				return layout.Dimensions{}
			}
			return p.hostname.Layout(gtx, th, "Hostname")
		}),
		layout.Rigid(layout.Spacer{Height: 10}.Layout),
		layout.Rigid(func(gtx C) D {
			return layout.Flex{
				Spacing: layout.SpaceBetween,
			}.Layout(gtx,
				layout.Flexed(1, material.Body1(th, "Use basic auth").Layout),
				layout.Rigid(material.Switch(th, &p.bBasicAuth, "use basic auth").Layout),
			)
		}),
		layout.Rigid(func(gtx C) D {
			if !p.bBasicAuth.Value {
				p.username.SetText("")
				return layout.Dimensions{}
			}
			return p.username.Layout(gtx, th, "Username")
		}),
		layout.Rigid(func(gtx C) D {
			if !p.bBasicAuth.Value {
				p.password.SetText("")
				return layout.Dimensions{}
			}

			if p.wgPassword.Clicked(gtx) {
				p.passwordVisible = !p.passwordVisible
			}

			if p.passwordVisible {
				p.password.Suffix = func(gtx layout.Context) layout.Dimensions {
					return p.wgPassword.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
						return icons.IconVisibility.Layout(gtx, color.NRGBA(colornames.Grey500))
					})
				}
				p.password.Mask = 0
			} else {
				p.password.Suffix = func(gtx layout.Context) layout.Dimensions {
					return p.wgPassword.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
						return icons.IconVisibilityOff.Layout(gtx, color.NRGBA(colornames.Grey500))
					})
				}
				p.password.Mask = '*'
			}

			return p.password.Layout(gtx, th, "Password")
		}),
		layout.Rigid(layout.Spacer{Height: 10}.Layout),
		layout.Rigid(func(gtx C) D {
			return layout.Flex{
				Spacing: layout.SpaceBetween,
			}.Layout(gtx,
				layout.Flexed(1, material.Body1(th, "Enable TLS").Layout),
				layout.Rigid(material.Switch(th, &p.bTLS, "enable TLS").Layout),
			)
		}),
	)
}

func (p *httpAddPage) createTunnel() error {
	var username, password string
	if p.bBasicAuth.Value {
		username = strings.TrimSpace(p.username.Text())
		password = strings.TrimSpace(p.password.Text())
	}
	var hostname string
	if p.bHost.Value {
		hostname = strings.TrimSpace(p.hostname.Text())
	}
	tun := tunnel.NewHTTPTunnel(
		tunnel.NameOption(strings.TrimSpace(p.name.Text())),
		tunnel.EndpointOption(strings.TrimSpace(p.addr.Text())),
		tunnel.UsernameOption(username),
		tunnel.PasswordOption(password),
		tunnel.HostnameOption(hostname),
		tunnel.EnableTLSOption(p.bTLS.Value),
	)

	tunnel.Add(tun)

	if err := tun.Run(); err != nil {
		tun.Close()
		return err
	}

	return nil
}

type httpEditPage struct {
	router *Router

	id string

	list       layout.List
	wgFavorite widget.Clickable
	wgState    widget.Clickable
	wgDelete   widget.Clickable
	wgDone     widget.Clickable

	name component.TextField
	addr component.TextField

	bHost    widget.Bool
	hostname component.TextField

	cbBasicAuth widget.Bool
	username    component.TextField
	password    component.TextField

	bTLS widget.Bool

	wgID         widget.Clickable
	wgEntrypoint widget.Clickable

	wgPassword      widget.Clickable
	passwordVisible bool
}

func NewHTTPEditPage(r *Router) Page {
	return &httpEditPage{
		router: r,
		list: layout.List{
			Axis:      layout.Vertical,
			Alignment: layout.Middle,
		},
		name: component.TextField{
			Editor: widget.Editor{
				SingleLine: true,
			},
		},
		addr: component.TextField{
			Editor: widget.Editor{
				SingleLine: true,
			},
		},
		hostname: component.TextField{
			Editor: widget.Editor{
				SingleLine: true,
			},
		},
		username: component.TextField{
			Editor: widget.Editor{
				SingleLine: true,
			},
		},
		password: component.TextField{
			Editor: widget.Editor{
				SingleLine: true,
			},
		},
	}
}

func (p *httpEditPage) Init(opts ...PageOption) {
	p.passwordVisible = false

	var options PageOptions
	for _, opt := range opts {
		opt(&options)
	}

	p.id = options.ID
	s := tunnel.Get(p.id)
	if s != nil {
		sopts := s.Options()
		p.name.SetText(sopts.Name)
		p.addr.SetText(sopts.Endpoint)
		if sopts.Hostname != "" {
			p.bHost.Value = true
			p.hostname.SetText(sopts.Hostname)
		}
		if sopts.Username != "" {
			p.cbBasicAuth.Value = true
			p.username.SetText(sopts.Username)
			p.password.SetText(sopts.Password)
		}
		p.bTLS.Value = sopts.EnableTLS
	}

	actions := []component.AppBarAction{
		{
			OverflowAction: component.OverflowAction{
				Name: "Favorite",
				Tag:  &p.wgFavorite,
			},
			Layout: func(gtx C, bg, fg color.NRGBA) D {
				s := tunnel.Get(p.id)
				if s == nil {
					return D{}
				}

				if p.wgFavorite.Clicked(gtx) {
					s.Favorite(!s.IsFavorite())
					tunnel.SaveConfig()
				}

				btn := component.SimpleIconButton(bg, fg, &p.wgFavorite, icons.IconFavorite)
				if s.IsFavorite() {
					btn.Color = color.NRGBA(colornames.Red500)
				} else {
					btn.Color = fg
				}
				return btn.Layout(gtx)
			},
		},
		{
			OverflowAction: component.OverflowAction{
				Name: "Start/Stop",
				Tag:  &p.wgState,
			},
			Layout: func(gtx C, bg, fg color.NRGBA) D {
				s := tunnel.Get(p.id)
				if p.wgState.Clicked(gtx) && s != nil {
					if s.IsClosed() {
						opts := s.Options()
						s = p.createTunnel(
							tunnel.NameOption(opts.Name),
							tunnel.IDOption(opts.ID),
							tunnel.EndpointOption(opts.Endpoint),
							tunnel.HostnameOption(opts.Hostname),
							tunnel.UsernameOption(opts.Username),
							tunnel.PasswordOption(opts.Password),
							tunnel.EnableTLSOption(opts.EnableTLS),
						)
					} else {
						s.Close()
					}
					tunnel.SaveConfig()
				}

				if s != nil && !s.IsClosed() {
					return component.SimpleIconButton(bg, fg, &p.wgState, icons.IconStop).Layout(gtx)
				} else {
					return component.SimpleIconButton(bg, fg, &p.wgState, icons.IconStart).Layout(gtx)
				}
			},
		},
		{
			OverflowAction: component.OverflowAction{
				Name: "Delete",
				Tag:  &p.wgDelete,
			},
			Layout: func(gtx C, bg, fg color.NRGBA) D {
				if p.wgDelete.Clicked(gtx) {
					tunnel.Delete(p.id)
					tunnel.SaveConfig()
					p.router.SwitchTo(Route{Path: PageTunnel})
				}
				return component.SimpleIconButton(bg, fg, &p.wgDelete, icons.IconDelete).Layout(gtx)
			},
		},
		{
			OverflowAction: component.OverflowAction{
				Name: "Save",
				Tag:  &p.wgDone,
			},
			Layout: func(gtx C, bg, fg color.NRGBA) D {
				if p.wgDone.Clicked(gtx) {
					defer p.router.SwitchTo(Route{Path: PageTunnel})

					if s := tunnel.Get(p.id); s != nil {
						s.Close()
						p.createTunnel()
						tunnel.SaveConfig()
					}
				}
				return component.SimpleIconButton(bg, fg, &p.wgDone, icons.IconDone).Layout(gtx)
			},
		},
	}
	p.router.bar.SetActions(actions, nil)
	p.router.bar.Title = "HTTP"
	p.router.bar.NavigationIcon = icons.IconClose
}

func (p *httpEditPage) createTunnel(opts ...tunnel.Option) tunnel.Tunnel {
	if opts == nil {
		var username, password string
		if p.cbBasicAuth.Value {
			username = strings.TrimSpace(p.username.Text())
			password = strings.TrimSpace(p.password.Text())
		}
		var hostname string
		if p.bHost.Value {
			hostname = strings.TrimSpace(p.hostname.Text())
		}

		opts = []tunnel.Option{
			tunnel.NameOption(strings.TrimSpace(p.name.Text())),
			tunnel.IDOption(p.id),
			tunnel.EndpointOption(strings.TrimSpace(p.addr.Text())),
			tunnel.UsernameOption(username),
			tunnel.PasswordOption(password),
			tunnel.HostnameOption(hostname),
			tunnel.EnableTLSOption(p.bTLS.Value),
		}
	}
	tun := tunnel.NewHTTPTunnel(opts...)

	tunnel.Set(tun)

	if err := tun.Run(); err != nil {
		tun.Close()
		log.Println(err)
	}

	return tun
}

func (p *httpEditPage) Layout(gtx C, th *material.Theme) D {
	return p.list.Layout(gtx, 1, func(gtx C, _ int) D {
		return layout.Center.Layout(gtx, func(gtx C) D {
			return layout.UniformInset(10).Layout(gtx, func(gtx C) D {
				return component.Surface(th).Layout(gtx, func(gtx C) D {
					return layout.UniformInset(10).Layout(gtx, func(gtx C) D {
						return p.layout(gtx, th)
					})
				})
			})
		})
	})
}

func (p *httpEditPage) layout(gtx C, th *material.Theme) D {
	return layout.Flex{
		Axis: layout.Vertical,
	}.Layout(gtx,
		layout.Rigid(func(gtx C) D {
			return layoutHeader(gtx, th, tunnel.Get(p.id), &p.wgID, &p.wgEntrypoint)
		}),
		layout.Rigid(layout.Spacer{Height: 10}.Layout),
		layout.Rigid(func(gtx C) D {
			return material.Body1(th, "Tunnel name").Layout(gtx)
		}),
		layout.Rigid(func(gtx C) D {
			return p.name.Layout(gtx, th, "Name")
		}),
		layout.Rigid(layout.Spacer{Height: 10}.Layout),
		layout.Rigid(func(gtx C) D {
			return material.Body1(th, "Endpoint address").Layout(gtx)
		}),
		layout.Rigid(func(gtx C) D {
			if err := func() error {
				addr := strings.TrimSpace(p.addr.Text())
				if addr == "" {
					return nil
				}
				if _, err := net.ResolveTCPAddr("tcp", addr); err != nil {
					return fmt.Errorf("invalid address format, should be [IP]:PORT or [HOST]:PORT")
				}
				return nil
			}(); err != nil {
				p.addr.SetError(err.Error())
			} else {
				p.addr.ClearError()
			}

			return p.addr.Layout(gtx, th, "Address")
		}),
		layout.Rigid(layout.Spacer{Height: 10}.Layout),
		layout.Rigid(func(gtx C) D {
			return layout.Inset{Top: 10, Bottom: 10}.Layout(gtx, func(gtx C) D {
				return layout.Flex{
					Spacing: layout.SpaceBetween,
				}.Layout(gtx,
					layout.Flexed(1, material.Body1(th, "Use custom hostname (rewrite HTTP Host header)").Layout),
					layout.Rigid(material.Switch(th, &p.bHost, "custom hostname").Layout),
				)
			})
		}),
		layout.Rigid(func(gtx C) D {
			if !p.bHost.Value {
				p.hostname.SetText("")
				return layout.Dimensions{}
			}
			return p.hostname.Layout(gtx, th, "Hostname")
		}),
		layout.Rigid(func(gtx C) D {
			return layout.Inset{Top: 10, Bottom: 10}.Layout(gtx, func(gtx C) D {
				return layout.Flex{
					Spacing: layout.SpaceBetween,
				}.Layout(gtx,
					layout.Flexed(1, material.Body1(th, "Use basic auth").Layout),
					layout.Rigid(material.Switch(th, &p.cbBasicAuth, "use basic auth").Layout),
				)
			})
		}),
		layout.Rigid(func(gtx C) D {
			if !p.cbBasicAuth.Value {
				p.username.SetText("")
				return layout.Dimensions{}
			}
			return p.username.Layout(gtx, th, "Username")
		}),
		layout.Rigid(func(gtx C) D {
			if !p.cbBasicAuth.Value {
				p.password.SetText("")
				return layout.Dimensions{}
			}

			if p.wgPassword.Clicked(gtx) {
				p.passwordVisible = !p.passwordVisible
			}

			if p.passwordVisible {
				p.password.Suffix = func(gtx layout.Context) layout.Dimensions {
					return p.wgPassword.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
						return icons.IconVisibility.Layout(gtx, color.NRGBA(colornames.Grey500))
					})
				}
				p.password.Mask = 0
			} else {
				p.password.Suffix = func(gtx layout.Context) layout.Dimensions {
					return p.wgPassword.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
						return icons.IconVisibilityOff.Layout(gtx, color.NRGBA(colornames.Grey500))
					})
				}
				p.password.Mask = '*'
			}

			return p.password.Layout(gtx, th, "Password")
		}),
		layout.Rigid(func(gtx C) D {
			return layout.Inset{Top: 10, Bottom: 10}.Layout(gtx, func(gtx C) D {
				return layout.Flex{
					Spacing: layout.SpaceBetween,
				}.Layout(gtx,
					layout.Flexed(1, material.Body1(th, "Enable TLS").Layout),
					layout.Rigid(material.Switch(th, &p.bTLS, "enable TLS").Layout),
				)
			})
		}),
	)
}
