package usecase

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

type googleIDTokenInfoResp struct {
	Audience string `json:"aud"`
	Issuer   string `json:"iss"`
	Subject  string `json:"sub"`
	Email    string `json:"email"`
	Name     string `json:"name"`
	Expires  string `json:"exp"`
}

func googleIDTokenInfo(ctx context.Context, idToken, clientID string) (*googleIDTokenInfoResp, error) {
	endpoint := "https://oauth2.googleapis.com/tokeninfo?id_token=" + url.QueryEscape(strings.TrimSpace(idToken))
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return nil, err
	}
	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("google tokeninfo status %d", resp.StatusCode)
	}
	var info googleIDTokenInfoResp
	if err := json.Unmarshal(body, &info); err != nil {
		return nil, err
	}
	if err := validateGoogleIDTokenInfo(&info, clientID, time.Now()); err != nil {
		return nil, err
	}
	return &info, nil
}

func validateGoogleIDTokenInfo(info *googleIDTokenInfoResp, clientID string, now time.Time) error {
	if info == nil || info.Subject == "" || info.Audience != strings.TrimSpace(clientID) {
		return fmt.Errorf("google ID token audience or subject is invalid")
	}
	if info.Issuer != "accounts.google.com" && info.Issuer != "https://accounts.google.com" {
		return fmt.Errorf("google ID token issuer is invalid")
	}
	expires, err := strconv.ParseInt(info.Expires, 10, 64)
	if err != nil || expires <= now.Unix() {
		return fmt.Errorf("google ID token is expired")
	}
	return nil
}

type lineTokenResp struct {
	AccessToken string `json:"access_token"`
}

type lineProfileResp struct {
	UserID      string `json:"userId"`
	DisplayName string `json:"displayName"`
}

func lineExchangeCode(ctx context.Context, code, redirectURI, clientID, clientSecret string) (string, error) {
	data := url.Values{}
	data.Set("grant_type", "authorization_code")
	data.Set("code", code)
	data.Set("redirect_uri", redirectURI)
	data.Set("client_id", clientID)
	data.Set("client_secret", clientSecret)
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.line.me/oauth2/v2.1/token",
		strings.NewReader(data.Encode()))
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("line token status %d: %s", resp.StatusCode, string(body))
	}
	var tok lineTokenResp
	if err := json.Unmarshal(body, &tok); err != nil {
		return "", err
	}
	if tok.AccessToken == "" {
		return "", fmt.Errorf("line empty access_token")
	}
	return tok.AccessToken, nil
}

func lineProfile(ctx context.Context, accessToken string) (*lineProfileResp, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.line.me/v2/profile", nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+accessToken)
	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("line profile status %d: %s", resp.StatusCode, string(body))
	}
	var p lineProfileResp
	if err := json.Unmarshal(body, &p); err != nil {
		return nil, err
	}
	if p.UserID == "" {
		return nil, fmt.Errorf("line profile missing userId")
	}
	return &p, nil
}
