// MIT License
//
// Copyright (c) 2025 chaunsin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

package ncmctl

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"strings"

	"github.com/chaunsin/netease-cloud-music/api"
	"github.com/chaunsin/netease-cloud-music/api/weapi"
	"github.com/chaunsin/netease-cloud-music/pkg/log"
	"github.com/chaunsin/netease-cloud-music/pkg/utils"

	"codeberg.org/sbinet/mozcookie"
	"github.com/spf13/cobra"
)

var cookieLongUse = `
Cookie-based login supports the following three content modes.

The --format parameter can be used for configuration.

* header

Content Examples:

uid=123456; PHPSESSID=28f2d88ee9322cfd2e4f1e; csrftoken=abcdef123456; logged_in=true

* netscaple

Content Examples:

# Netscape HTTP Cookie File
# This file was generated by Cookie Editor https://chromewebstore.google.com/detail/cookie-editor/ookdjilphngeeeghgngjabigmpepanpl
.163.com	TRUE	/	FALSE	1775732160	_ntes_nuid	47eabde2b621ff2848404b66e558ca99
.music.163.com	TRUE	/	FALSE	1775732161	NMTID	00Onhjq5uaKix1KMUPyrlAkMRkv_VgAAAGVZfK6jQ
.music.163.com	TRUE	/	FALSE	1780457981	WEVNSM	1.0.0
.music.163.com	TRUE	/	FALSE	1775732750	WNMCID	hyazcy.1741172750877.01.0

Reference: 

- https://curl.se/rfc/cookie_spec.html
- http://justsolve.archiveteam.org/wiki/Netscape_cookies.txt
- https://docs.cyotek.com/cyowcopy/1.10/netscapecookieformat.html

* json

Content Examples:

[
  {
    "domain": ".163.com",
    "expirationDate": 1775732160.825451,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_ntes_nnid",
    "path": "/",
    "sameSite": "unspecified",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "47eabde2b621ff2848404b66e558ca99,1741172160825"
  },
  {
    "domain": ".163.com",
    "expirationDate": 1775732160.825588,
    "hostOnly": false,
    "httpOnly": false,
    "name": "_ntes_nuid",
    "path": "/",
    "sameSite": "unspecified",
    "secure": false,
    "session": false,
    "storeId": "0",
    "value": "47eabde2b621ff2848404b66e558ca99"
  }
]

* default:

Automatically attempt json, header, and netscape.
`

type loginCookieCmd struct {
	root *Login
	cmd  *cobra.Command
	l    *log.Logger

	File   string
	format string
}

func cookie(root *Login, l *log.Logger) *cobra.Command {
	c := &loginCookieCmd{
		root: root,
		l:    l,
	}
	c.cmd = &cobra.Command{
		Use:     "cookie",
		Short:   "use cookie login",
		Long:    cookieLongUse,
		Example: "  ncmctl login cookie -f cookie.txt\n  ncmctl login cookie --format netscaple -f cookie.json\n  ncmctl login cookie 'value'\n  ncmctl login cookie --format json 'value'",
		RunE: func(cmd *cobra.Command, args []string) error {
			return c.execute(cmd.Context(), args)
		},
	}
	c.addFlags()
	return c.cmd
}

func (c *loginCookieCmd) addFlags() {
	c.cmd.Flags().StringVarP(&c.File, "file", "f", "", "import cookie file")
	c.cmd.Flags().StringVar(&c.format, "format", "", "import cookie file format. eg: ''、json、netscaple、header")
}

func (c *loginCookieCmd) execute(ctx context.Context, args []string) error {
	var content string
	if len(args) > 0 {
		content = args[0]
	}
	if c.File == "" && content == "" {
		return fmt.Errorf("file is required or imput cookie string")
	}
	if c.format != "" &&
		c.format != "json" &&
		c.format != "netscape" &&
		c.format != "header" {
		return fmt.Errorf("format is not support: %v", c.format)
	}

	log.Debug("args: format=%v, file=%v, content=%v", c.format, c.File, content)

	var cookies []*http.Cookie
	if c.File != "" {
		file, err := utils.ExpandTilde(c.File)
		if err != nil {
			return fmt.Errorf("ExpandTilde: %w", err)
		}
		c.File = file

		switch c.format {
		case "json":
			f, err := os.Open(c.File)
			if err != nil {
				return fmt.Errorf("open: %w", err)
			}
			defer f.Close()

			cookies, err = ParseCookeJson(f)
			if err != nil {
				return fmt.Errorf("ParseCookeJson: %w", err)
			}
		case "netscape":
			ck, err := mozcookie.Read(c.File)
			if err != nil {
				return fmt.Errorf("mozcookie.Read: %w", err)
			}
			cookies = ck
		case "header":
			ck, err := http.ParseCookie(content)
			if err != nil {
				return fmt.Errorf("ParseCookie: %ww", err)
			}
			cookies = ck
		default:
			// 走探测逻辑
			ck, err := mozcookie.Read(c.File)
			if err != nil {
				log.Debug("retry read netscape err: %s", err)
			}
			cookies = ck
			if len(cookies) <= 0 {
				f, err := os.Open(c.File)
				if err != nil {
					return fmt.Errorf("open: %w", err)
				}
				defer f.Close()

				cookies, err = ParseCookeJson(f)
				if err != nil {
					log.Debug("retry parse json err: %s", err)
				}
			}
			if len(cookies) <= 0 {
				data, err := os.ReadFile(c.File)
				if err != nil {
					return fmt.Errorf("open: %w", err)
				}

				cookies, err = http.ParseCookie(string(data))
				if err != nil {
					return fmt.Errorf("ParseCookie: %w", err)
				}
			}
		}
	} else {
		var binary = strings.NewReader(content)
		switch c.format {
		case "json":
			ck, err := ParseCookeJson(binary)
			if err != nil {
				return fmt.Errorf("ParseCookeJson: %w", err)
			}
			cookies = ck
		case "netscape":
			ck, err := mozcookie.Decode(binary)
			if err != nil {
				return fmt.Errorf("mozcookie.Decode: %w", err)
			}
			cookies = ck
		case "header":
			ck, err := http.ParseCookie(content)
			if err != nil {
				return fmt.Errorf("ParseCookie: %w", err)
			}
			cookies = ck
		default:
			// 走探测逻辑
			ck, err := mozcookie.Decode(binary)
			if err != nil {
				log.Debug("retry decode netscape err: %s", err)
			}
			cookies = ck
			if len(cookies) <= 0 {
				cookies, err = ParseCookeJson(binary)
				if err != nil {
					log.Debug("retry parse json ere: %s", err)
				}
			}
			if len(cookies) <= 0 {
				cookies, err = http.ParseCookie(content)
				if err != nil {
					return fmt.Errorf("ParseCookie: %w", err)
				}
			}
		}
	}

	if len(cookies) <= 0 {
		return fmt.Errorf("cookie is empty")
	}
	var hasToken bool
	for _, v := range cookies {
		if v.Name == "MUSIC_U" {
			hasToken = true
			break
		}
	}
	if !hasToken {
		return fmt.Errorf("cookie not found MUSIC_U value")
	}

	// Parse the domain into a URL (adjust a scheme if needed)
	u, err := url.Parse("https://music.163.com")
	if err != nil {
		return fmt.Errorf("failed to parse domain URL: %v", err)
	}

	cli, err := api.NewClient(c.root.root.Cfg.Network, c.l)
	if err != nil {
		return fmt.Errorf("NewClient: %w", err)
	}
	defer cli.Close(ctx)

	cli.SetCookies(u, cookies)

	// 查询登录信息是否成功
	request := weapi.New(cli)
	user, err := request.GetUserInfo(ctx, &weapi.GetUserInfoReq{})
	if err != nil {
		return fmt.Errorf("GetUserInfo: %s", err)
	}
	c.cmd.Printf("login success: %+v\n", user)
	return nil
}
