package controller

import (
	"crawler/config"
	"crawler/model"
	"crawler/service"
	"crawler/utility"
	"log"
	"net"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gocolly/colly"
)

type MovieController interface {
	SearchDailyBoxOfficeList(c *gin.Context)
	SearchWeeklylyBoxOfficeList(c *gin.Context)
	SearchMovieList(c *gin.Context)
}

type Movie struct {
	MovieListModel   model.MovieListModel
	MovieDailyModel  model.MovieDailyModel
	MovieWeeklyModel model.MovieWeeklyModel
	MovieInfoModel   model.MovieInfoModel
	MovieSearchModel model.MovieSearchModel
	MovieDetailModel model.MovieDetailModel
	Kobis            model.Kobis
	Rest             service.Rest
}

// GET SearchDailyBoxOfficeList
func (this *Movie) SearchDailyBoxOfficeList(c *gin.Context) {
	var req = this.MovieDailyModel.SearchDailyBoxOfficeParams

	if err := c.ShouldBind(&req); err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	var (
		total, rows = 0, 0
		list        = make([]model.MovieDailyTable, 0)
	)

	// 검색 변수 생성
	params, err := this.MovieDailyModel.MakeParams(req)
	if err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	// 검색값이 있는지 확인
	ids, err := this.MovieSearchModel.SelectIDs(params)
	if err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	// 검색 내역이 있음
	if ids != "" {
		// DB에서 먼저 조회해본다.
		list, err = this.MovieDailyModel.List(ids)
		if err != nil {
			c.JSON(http.StatusBadRequest, err.Error())
			return
		}
	}

	// 목록이 없으면 API 호출
	if len(list) <= 0 {
		data, err := this.Kobis.MovieDailyBoxOfficeListAPI(req)
		if err != nil {
			c.JSON(http.StatusBadRequest, err.Error())
			return
		}

		// 데이터 새로 입력
		if len(data.BoxOfficeResult.DailyBoxOfficeList) > 0 {
			if err = this.MovieDailyModel.Insert(data); err != nil {
				c.JSON(http.StatusBadRequest, err.Error())
				return
			}

			var movieCd []string
			for _, row := range data.BoxOfficeResult.DailyBoxOfficeList {
				movieCd = append(movieCd, row.MovieCd)
			}

			lastInsertIds, _ := this.MovieDailyModel.LastInsertIDs(movieCd, req.TargetDt)
			if err != nil {
				c.JSON(http.StatusBadRequest, err.Error())
				return
			}

			// 검색 결과 저장
			movieSearch := this.MovieSearchModel.MovieSearch
			movieSearch.Params = params
			movieSearch.IDs = lastInsertIds
			if err = this.MovieSearchModel.Insert(movieSearch); err != nil {
				c.JSON(http.StatusBadRequest, err.Error())
				return
			}

			if lastInsertIds != "" {
				list, err = this.MovieDailyModel.List(lastInsertIds)
				if err != nil {
					c.JSON(http.StatusBadRequest, err.Error())
					return
				}
			}
		}
	}

	// 상세 정보를 조회해서 저장한다.
	for i, row := range list {
		row.Detail, _ = this.FindMovieDetail(row.MovieCd)
		list[i] = row
		rows++
	}

	total = this.MovieDailyModel.Total()

	c.JSON(http.StatusOK, gin.H{
		"total": total,
		"rows":  rows,
		"list":  list,
	})
}

// GET SearchWeeklylyBoxOfficeList
func (this *Movie) SearchWeeklyBoxOfficeList(c *gin.Context) {
	var req = this.MovieWeeklyModel.SearchWeeklyBoxOfficeParams

	if err := c.ShouldBind(&req); err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	var (
		total, rows = 0, 0
		list        = make([]model.MovieWeeklyTable, 0)
	)

	// 검색 변수 생성
	params, err := this.MovieWeeklyModel.MakeParams(req)
	if err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	// 검색값이 있는지 확인
	ids, err := this.MovieSearchModel.SelectIDs(params)
	if err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	// 검색 내역이 있음
	if ids != "" {
		// DB에서 먼저 조회해본다.
		list, err = this.MovieWeeklyModel.List(ids)
		if err != nil {
			c.JSON(http.StatusBadRequest, err.Error())
			return
		}
	}

	// 목록이 없으면 API 호출
	if len(list) <= 0 {
		data, err := this.Kobis.MovieWeeklyBoxOfficeListAPI(req)

		if err != nil {
			c.JSON(http.StatusBadRequest, err.Error())
			return
		}

		// 데이터 새로 입력
		if len(data.BoxOfficeResult.WeeklyBoxOfficeList) > 0 {
			if err = this.MovieWeeklyModel.Insert(data); err != nil {
				c.JSON(http.StatusBadRequest, err.Error())
				return
			}

			var movieCd []string
			for _, row := range data.BoxOfficeResult.WeeklyBoxOfficeList {
				movieCd = append(movieCd, row.MovieCd)
			}

			lastInsertIds, _ := this.MovieWeeklyModel.LastInsertIDs(movieCd, req.TargetDt)
			if err != nil {
				c.JSON(http.StatusBadRequest, err.Error())
				return
			}

			// 검색 결과 저장
			movieSearch := this.MovieSearchModel.MovieSearch
			movieSearch.Params = params
			movieSearch.IDs = lastInsertIds
			if err = this.MovieSearchModel.Insert(movieSearch); err != nil {
				c.JSON(http.StatusBadRequest, err.Error())
				return
			}

			if lastInsertIds != "" {
				list, err = this.MovieWeeklyModel.List(lastInsertIds)
				if err != nil {
					c.JSON(http.StatusBadRequest, err.Error())
					return
				}
			}
		}
	}

	// 상세 정보를 조회해서 저장한다.
	for i, row := range list {
		row.Detail, _ = this.FindMovieDetail(row.MovieCd)
		list[i] = row
		rows++
	}

	total = this.MovieWeeklyModel.Total()

	c.JSON(http.StatusOK, gin.H{
		"total": total,
		"rows":  rows,
		"list":  list,
	})
}

// GET SearchMovieList
func (this *Movie) SearchMovieList(c *gin.Context) {
	var req = this.MovieListModel.SearchMovieListParams

	if err := c.ShouldBind(&req); err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	type result = struct {
		MovieListInfo model.MovieListInfo     `json:"summary"`
		Info          *model.MovieInfoTable   `json:"info"`
		Detail        *model.MovieDetailTable `json:"detail"`
	}

	var (
		total, rows = 0, 0
		list        = make([]result, 0)
	)

	data, err := this.Kobis.MovieListAPI(req)
	if err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	// 데이터 새로 입력
	if data.MovieListResult.TotCnt > 0 {
		if err = this.MovieListModel.Insert(data.MovieListResult.MovieList); err != nil {
			c.JSON(http.StatusBadRequest, err.Error())
			return
		}
	}

	// 기본, 상세 정보를 조회해서 저장한다.
	for _, row := range data.MovieListResult.MovieList {
		info, _ := this.FindMovieInfo(req.Key, row.MovieCd)
		detail, _ := this.FindMovieDetail(row.MovieCd)

		list = append(list, result{
			MovieListInfo: row,
			Info:          info,
			Detail:        detail,
		})
		rows++
	}

	total = this.MovieListModel.Total()

	c.JSON(http.StatusOK, gin.H{
		"total": total,
		"rows":  rows,
		"list":  list,
	})
}

/**
 * 영화 일별 박스오피스 정보 조회
 * /movie/searchDailyInfo
 */
func (this *Movie) SearchDailyInfo(c *gin.Context) {
	var req = struct {
		Key     string `form:"key" binding:"required"`
		DailyID int    `form:"dailyID" binding:"required"`
	}{}

	if err := c.ShouldBind(&req); err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	stats, err := this.MovieDailyModel.Info(req.DailyID)
	if err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	if stats.DailyID == 0 {
		c.JSON(http.StatusBadRequest, "잘못된 요청입니다.")
		return
	}

	info, err := this.FindMovieInfo(req.Key, stats.MovieCd)
	if err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	detail, err := this.FindMovieDetail(stats.MovieCd)
	if err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"stats":  stats,
		"info":   info,
		"detail": detail,
	})
}

/**
 * 영화 주간/주말 박스오피스 정보 조회
 * /movie/searchWeeklyInfo
 */
func (this *Movie) SearchWeeklyInfo(c *gin.Context) {
	var req = struct {
		Key      string `form:"key" binding:"required"`
		WeeklyID int    `form:"weeklyID" binding:"required"`
	}{}

	if err := c.ShouldBind(&req); err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	stats, err := this.MovieWeeklyModel.Info(req.WeeklyID)
	if err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	if stats.WeeklyID == 0 {
		c.JSON(http.StatusBadRequest, "잘못된 요청입니다.")
		return
	}

	info, err := this.FindMovieInfo(req.Key, stats.MovieCd)
	if err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	detail, err := this.FindMovieDetail(stats.MovieCd)
	if err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"stats":  stats,
		"info":   info,
		"detail": detail,
	})
}

/**
 * 영화 정보 조회
 * /movie/searchMovieInfo
 */
func (this *Movie) SearchMovieInfo(c *gin.Context) {
	var req = this.MovieInfoModel.SearchMovieInfoParams

	if err := c.ShouldBind(&req); err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	info, err := this.FindMovieInfo(req.Key, req.MovieCd)
	if err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	detail, err := this.FindMovieDetail(req.MovieCd)
	if err != nil {
		c.JSON(http.StatusBadRequest, err.Error())
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"info":   info,
		"detail": detail,
	})
}

// 영화 기본 정보 조회
func (this *Movie) FindMovieInfo(key, movieCd string) (*model.MovieInfoTable, error) {
	if this.MovieListModel.IsExists(movieCd) == false || this.MovieInfoModel.IsExists(movieCd) == false {
		req := this.MovieInfoModel.SearchMovieInfoParams
		req.Key = key
		req.MovieCd = movieCd
		data, err := this.Kobis.MovieInfoAPI(req)
		if err != nil {
			return nil, err
		}

		err = this.MovieListModel.Replace(model.MovieListInfo{
			MovieCd:     data.MovieInfoResult.MovieInfo.MovieCd,
			MovieNm:     data.MovieInfoResult.MovieInfo.MovieNm,
			MovieNmEn:   data.MovieInfoResult.MovieInfo.MovieNmEn,
			PrdtYear:    data.MovieInfoResult.MovieInfo.PrdtYear,
			OpenDt:      data.MovieInfoResult.MovieInfo.OpenDt,
			TypeNm:      data.MovieInfoResult.MovieInfo.TypeNm,
			PrdtStatNm:  data.MovieInfoResult.MovieInfo.PrdtStatNm,
			NationAlt:   "",
			GenreAlt:    "",
			RepNationNm: "",
			RepGenreNm:  "",
			Directors:   nil,
			Companys:    nil,
		})

		if err != nil {
			return nil, err
		}

		// 영화 기본 정보 입력
		this.MovieInfoModel.Insert(data.MovieInfoResult.MovieInfo)

		if err != nil {
			return nil, err
		}
	}

	movieInfo, err := this.MovieInfoModel.Info(movieCd)

	return &movieInfo, err
}

// 영화 주요 정보 조회
func (this *Movie) FindMovieDetail(movieCd string) (*model.MovieDetailTable, error) {
	if this.MovieDetailModel.IsExists(movieCd) == false {
		movieDetail := this.MovieDetailModel.MovieDetail
		movieDetail.MovieCd = movieCd

		// c2 := c1.Clone()

		c1 := colly.NewCollector(
			colly.AllowedDomains(config.KOBIS_DOMAIN),
			colly.IgnoreRobotsTxt(),
			colly.Async(false),
		)

		c1.WithTransport(&http.Transport{
			DialContext: (&net.Dialer{
				Timeout:   30 * time.Second,
				KeepAlive: 30 * time.Second,
			}).DialContext,
			MaxIdleConns:          0,
			IdleConnTimeout:       10 * time.Second,
			TLSHandshakeTimeout:   10 * time.Second,
			ExpectContinueTimeout: 10 * time.Second,
		})

		c1.OnRequest(func(r *colly.Request) {
			r.Headers.Set("User-Agent", utility.RandomString())
			r.Headers.Set("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
		})

		c1.OnError(func(_ *colly.Response, err error) {
			log.Printf("Error(c1) : %s\n", err.Error())
		})

		/*
		 관객 수, 누적 매출액 조회
		*/
		/*
			c2.OnRequest(func(r *colly.Request) {
				r.Headers.Set("User-Agent", utility.RandomString())
				r.Headers.Set("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
			})

			c2.OnError(func(_ *colly.Response, err error) {
				log.Printf("Error(c2) : %s\n", err.Error())
			})
		*/

		c1.OnHTML(".item_tab.basic", func(e *colly.HTMLElement) {
			var host = config.KOBIS_HOST
			movieDetail.MainImg = e.ChildAttr("a.fl.thumb", "href")
			if movieDetail.MainImg != "" && movieDetail.MainImg != "#" {
				movieDetail.MainImg = host + movieDetail.MainImg
			}
			movieDetail.ThumbImg = e.ChildAttr("a.fl.thumb > img", "src")
			if movieDetail.ThumbImg != "" && movieDetail.ThumbImg != "#" {
				movieDetail.ThumbImg = host + movieDetail.ThumbImg
			}
			movieDetail.Synopsis = e.ChildText("div.info.info2 p.desc_info")

			e.ForEach("div#post > input", func(_ int, ee *colly.HTMLElement) {
				movieDetail.Poster = append(movieDetail.Poster, model.Poster{
					Thumb:  host + ee.Attr("thn_img"),
					Origin: host + ee.Attr("img"),
				})
			})
			e.ForEach("div#stl > input", func(_ int, ee *colly.HTMLElement) {
				movieDetail.StillCut = append(movieDetail.StillCut, model.StillCut{
					Thumb:  host + ee.Attr("thn_img"),
					Origin: host + ee.Attr("img"),
				})
			})
		})

		/*
			c2.OnHTML("body", func(e *colly.HTMLElement) {
				var (
					tr      = e.DOM.Find(".info").Eq(0).Find("table tbody tr").Eq(1)
					saleAcc = utility.RemoveSpecialChar(strings.Replace(tr.Find("td").Eq(2).Text(), "(100%)", "", 1))
					audiAcc = utility.RemoveSpecialChar(strings.Replace(tr.Find("td").Eq(3).Text(), "(100%)", "", 1))
				)
				SaleAcc, _ := strconv.Atoi(saleAcc)
				AudiAcc, _ := strconv.Atoi(audiAcc)

				movieDetail.SaleAcc = SaleAcc
				movieDetail.AudiAcc = AudiAcc
			})
		*/

		err := c1.Post(config.MOVIE_DETAIL, map[string]string{
			"code":       movieCd,
			"sType":      "",
			"titleYN":    "Y",
			"etcParam":   "",
			"isOuterReq": "false",
		})
		if this.Rest.Check(err) {
			return nil, err
		}

		/*
			err = c2.Post(config.MOVIE_DETAIL, map[string]string{
				"code":  movieCd,
				"sType": "stat",
			})
			if this.Rest.Check(err) {
				return nil, err
			}
		*/

		err = this.MovieDetailModel.Insert(movieDetail)
		if this.Rest.Check(err) {
			return nil, err
		}
	}

	movieDetail, err := this.MovieDetailModel.Info(movieCd)

	return &movieDetail, err
}
