// mautrix-telegram - A Matrix-Telegram puppeting bridge.
// Copyright (C) 2024 Sumner Evans
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package connector

import (
	"context"
	"fmt"
	"net/http"
	"sync"
	"time"

	"maunium.net/go/mautrix/bridgev2"
	"maunium.net/go/mautrix/bridgev2/database"

	"go.mau.fi/mautrix-telegram/pkg/connector/ids"
	"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)

const (
	LoginFlowIDPhone = "phone"
	LoginFlowIDQR    = "qr"

	LoginStepIDComplete = "fi.mau.telegram.login.complete"
)

var (
	ErrInvalidPassword = bridgev2.RespError{
		ErrCode:    "FI.MAU.TELEGRAM.INVALID_PASSWORD",
		Err:        "Invalid password",
		StatusCode: http.StatusBadRequest,
	}
	ErrPhoneCodeInvalid = bridgev2.RespError{
		ErrCode:    "FI.MAU.TELEGRAM.PHONE_CODE_INVALID",
		Err:        "Invalid phone code",
		StatusCode: http.StatusBadRequest,
	}
	ErrSignUpNotSupported = bridgev2.RespError{
		ErrCode:    "FI.MAU.TELEGRAM.SIGN_UP_NOT_SUPPORTED",
		Err:        "New account creation is not supported",
		StatusCode: http.StatusBadRequest,
	}
)

func (tg *TelegramConnector) GetLoginFlows() []bridgev2.LoginFlow {
	return []bridgev2.LoginFlow{
		{
			Name:        "Phone Number",
			Description: "Login using your Telegram phone number",
			ID:          LoginFlowIDPhone,
		},
		{
			Name:        "QR Code",
			Description: "Login by scanning a QR code from your phone",
			ID:          LoginFlowIDQR,
		},
	}
}

func (tg *TelegramConnector) CreateLogin(ctx context.Context, user *bridgev2.User, flowID string) (bridgev2.LoginProcess, error) {
	switch flowID {
	case LoginFlowIDPhone:
		return &PhoneLogin{user: user, main: tg}, nil
	case LoginFlowIDQR:
		return &QRLogin{user: user, main: tg}, nil
	default:
		return nil, fmt.Errorf("unknown flow ID %s", flowID)
	}
}

func finalizeLogin(ctx context.Context, user *bridgev2.User, authorization *tg.AuthAuthorization, metadata UserLoginMetadata) (*bridgev2.LoginStep, error) {
	userLoginID := ids.MakeUserLoginID(authorization.User.GetID())
	ul, err := user.NewLogin(ctx, &database.UserLogin{
		ID:       userLoginID,
		Metadata: &metadata,
	}, &bridgev2.NewLoginParams{
		DeleteOnConflict: true,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to save new login: %w", err)
	}
	ul.Client.Connect(ul.Log.WithContext(ctx))
	client := ul.Client.(*TelegramClient)

	// Connecting is non-blocking so wait for gotd to initialize before doing anythign to avoid deadlocking
	err = client.clientInitialized.Wait(ctx)
	if err != nil {
		return nil, err
	}

	me, err := client.client.Self(ctx)
	if err != nil {
		return nil, err
	}
	go func() {
		log := ul.Log.With().Str("component", "login_sync_chats").Logger()
		if err := client.SyncChats(log.WithContext(client.clientCtx)); err != nil {
			log.Err(err).Msg("Failed to sync chats")
		}
	}()

	go func() {
		log := ul.Log.With().Str("component", "login_takeout").Logger()
		client.takeoutLock.Lock()
		defer client.takeoutLock.Unlock()
		_, err = client.getTakeoutID(ctx)
		if err != nil {
			log.Err(err).Msg("Failed to get takeout")
			return
		}
		if client.stopTakeoutTimer == nil {
			client.stopTakeoutTimer = time.AfterFunc(max(time.Hour, time.Duration(client.main.Bridge.Config.Backfill.Queue.BatchDelay*2)), sync.OnceFunc(func() { client.stopTakeout(ctx) }))
		} else {
			client.stopTakeoutTimer.Reset(max(time.Hour, time.Duration(client.main.Bridge.Config.Backfill.Queue.BatchDelay*2)))
		}
	}()

	ul.RemoteProfile, ul.RemoteName = userToRemoteProfile(me, nil, nil)
	err = ul.Save(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to save login: %w", err)
	}

	return &bridgev2.LoginStep{
		Type:         bridgev2.LoginStepTypeComplete,
		StepID:       LoginStepIDComplete,
		Instructions: fmt.Sprintf("Successfully logged in as %s (`%d`)", ul.RemoteName, me.ID),
		CompleteParams: &bridgev2.LoginCompleteParams{
			UserLoginID: ul.ID,
			UserLogin:   ul,
		},
	}, nil
}
